1.泛型与类型擦除

泛型的本质是参数化类型的应用,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。在泛型没有出现之前,只能通过Object是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化,由于java语言所有的类型都继承自Object,因此Object转型成任何对象都是有可能的,但是也因为有无限的可能性,就只有程序员和运行期的虚拟机才知道这个Object到底是个什么类型的对象。在编译期间,编译器无法检查这个Object的强制转换是否成功,只能寄托于程序员不会出错,许多ClassCatException的风险就会转嫁到程序运行期之中

C#的泛型在程序源码在、编译后的IL中,或是运行期的CLR中,都是切实存在的,List<int>和List<String>就是两种不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型称为真实泛型

java语言中的泛型不一样,只在源码中存在,在编译后的字节码文件中,已经替换成了原来的原生类型(也称为裸类型)了,并且在相应的地方插入了强制转化代码,因此,对于运行期的Java语言来说,ArrayList<int>和ArrayList<String>就是同一个类,所以泛型技术实际上是java语言的一颗语法糖,java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型。

public static void main(String[] args){

        Map<String,String> map=new HashMap<String,String>();
map.put("hello","nihao");
map.put("how are you","chifan");
System.out.println(map.get("hello"));
System.out.println(map.get("how are you")); }

这段代码编译成Class文件,然后用字节码反编译工具进行反编译后,泛型类型都变回了原生类型。

public static void main(String[] args){

        Map map=new HashMap();
map.put("hello","nihao");
map.put("how are you","chifan");
System.out.println((String)map.get("hello"));
System.out.println((String)map.get("how are you")); }

通过擦除法来实现泛型丧失了一些泛型思想应有的优雅。

public class G{

   public static void method(List<String> list){

       //输出1
}
public static void method(List<Integer> list){ //输出2 } }

很明显不能编译通过,因为擦除以后两种方法的方法签名变得一模一样。好像不能重载的原因找到了?只能说泛型擦除成相同的原生类型这时无法重载的一部分原因,继续看

public class G{

   public static String method(List<String> list){

       //输出1
return ""; }
public static int method(List<Integer> list){ //输出2
return 1;
}
public static void main(String[] args){ method(new ArrayList<String>()):
method(new ArrayList<Integer>()):
} }

执行结果


因为两个返回值的加入,方法重载居然成功了。这是对java语言中返回值不参与重载选择的基本认知的挑战吗?

之所以能够编译和执行成功,是因为两个method()方法中加入了不同的返回值后才能够共存在一个Class文件之中。Class文件方法表中提到过,方法重载要求方法具备不同的特征签名,返回值并不包括在方法的特征签名里,所以返回值不参与重载选择,但是在Class文件格式之中,只要描述符不是完全一致的两个方法就可以共存,也就是说,两个方法如果相同的名称和特征签名,但返回值不同,那他们也是可以合法地共存于一个Cass文件中的!

!!!擦拭法所谓的擦除,仅仅是对方法的code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能够通过反射手段取得参数化类型的根本依据。

既然所有的泛型都会被擦拭,为什么不能往List<String>里面加int类型的数据呢?在Myeclipse里面这样会直接报错,说明是在编译之前就进行的,我想不是应该发生在语义分析里面的标注检查那个阶段么??解除语法糖发生在语义分析之后,说明之前会发生标注检查,应该就是这样吧。

②自动装箱、拆箱和遍历循环

java语言里面使用最多的语法糖。

public static void main(String[] args){

 List<Integer> list=Arrays.asList(,,,);
int sum=;
for(int i:list){
sum+=i; } }

解除语法糖之后

public static void main(String[] args){

 List list=Arrays.asList(new Integer[]{
Integet.valueOf();
Integet.valueOf();
Integet.valueOf();
Integet.valueOf();
}
int sum=;
for(Iterator l=list.iterator();l.hasNext();){ int i=((Ingeter)l.next()).intValue();
sum+=;
} }

变长参数调用的时候变成了一个数组类型的参数,Integer.valueOf()与Integer.intValue()为包装和还原方法。

自动装箱的错误用法

  public static void main(String[] args){
Integer a=;
Integer b=;
Integer c=;
Integer d=;
Integer e=;
Integer f=;
Long g=3L;
System.out.println(c==d);
System.out.println(e==f);
System.out.println(c==(a+b));
System.out.println(c.equals(a+b));
System.out.println(g==(a+b));
System.out.println(g.equals(a+b));
}
true
false
true
true
true
false

包装类的“==”运算在不遇到算术运算的情况下不会自动装箱,以及它们的equals()方法不处理数据类型转型的关系。

1. 首先我们明确一下"=="和equals方法的作用。

  "==":如果是基本数据类型,则直接对值进行比较,如果是引用数据类型,则是对他们的地址进行比较(但是只能比较相同类型的对象,或者比较父类对象和子类对象。类型不同的两个对象不能使用==)

  equals方法继承自Object类,在具体实现时可以覆盖父类中的实现。看一下Object中qeuals的源码发现,它的实现也是对对象的地址进行比较,此时它和"=="的作用相同。而JDK类中有一些类覆盖了Object类的equals()方法,比较规则为:如果两个对象的类型一致,并且内容一致,则返回true,这些类有:
java.io.file,java.util.Date,java.lang.string,包装类(Integer,Double等)。

2. Java的包装类实现细节。观察源码会发现Integer包装类中定义了一个私有的静态内部类如下:

 1 private static class IntegerCache {
2 static final int low = -128;
3 static final int high;
4 static final Integer cache[];
5
6 static {
7 // high value may be configured by property
8 int h = 127;
9 String integerCacheHighPropValue =
10 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
11 if (integerCacheHighPropValue != null) {
12 try {
13 int i = parseInt(integerCacheHighPropValue);
14 i = Math.max(i, 127);
15 // Maximum array size is Integer.MAX_VALUE
16 h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
17 } catch( NumberFormatException nfe) {
18 // If the property cannot be parsed into an int, ignore it.
19 }
20 }
21 high = h;
22
23 cache = new Integer[(high - low) + 1];
24 int j = low;
25 for(int k = 0; k < cache.length; k++)
26 cache[k] = new Integer(j++);
27
28 // range [-128, 127] must be interned (JLS7 5.1.7)
29 assert IntegerCache.high >= 127;
30 }
31
32 private IntegerCache() {}
33 }

而Integer的自动装箱代码:

1 public static Integer valueOf(int i) {
2 if (i >= IntegerCache.low && i <= IntegerCache.high)
3 return IntegerCache.cache[i + (-IntegerCache.low)];
4 return new Integer(i);
5 }

通过观察上面的代码我们可以发现,Integer使用一个内部静态类中的一个静态数组保存了-128-127范围内的数据,静态数组在类加载以后是存在方法区的,并不是什么常量池。在自动装箱的时候,首先判断要装箱的数字的范围,如果在-128-127的范围则直接返回缓存中已有的对象,否则new一个新的对象。其他的包装类也有类似的实现方式,可以通过源码观察一下。

3. "=="在遇到非算术运算符的情况下不会自动拆箱,以及他们的equals方法不处理数据类型转换的关系。

因此,对于 System.out.println(c == d); 他们指向同一个对象,返回True。

对于 System.out.println(e == f); 他们的值大于127,即使值相同,但是对应不同的内存地址,返回false。

对于 System.out.println(c == (a+b)); 自动拆箱后他们的值是相等的,返回True。

对于 System.out.println(c.equals(a+b)); 他们的值相同,而且类型相同,返回true。

对于 System.out.println(g == (a+b)); 自动拆箱后他们的值相等,返回True。

对于 System.out.println(g.equals(a+b)); 他们的值相同但是类型不同,返回false。

③条件编译

(—般情况下,C语言源程序中的每一行代码.都要参加编译。但有时候出于对程序代码优化的考虑.希望只对其中一部分内容进行编译.此时就需要在程序中加上条件,让编译器只对满足条件的代码进行编译,将不满足条件的代码舍弃,这就是条件编译)

C、C++使用预处理器指示符(#ifdef)来完成条件编译。而在java预言之中没有使用预处理器,因为java语言天然的编译方式(编译器并非一个个地编译java文件,而是将所有编译单元的语法树顶级节点输入到待处理列表后再进行编译,因此各个文件之间能够互相提供符号信息)无须使用预处理器。

java想实现条件编译,方法就是使用条件为常量的if语句。这时if语句不通气其他java代码,它在编译阶段就会被“运行”,生成字节码之中只包括if里面的内容,不包括它分支else里面的内容

public static void main(String[] args){

    if(true){
System.out.println("bolck 1");
}else{
System.out.println("bolck 2");
} }

编译后会变成

public static void main(String[] args){
System.out.println("bolck 1"): }

早期(编译器)优化--Java语法糖的味道的更多相关文章

  1. Java语法糖1:可变长度参数以及foreach循环原理

    语法糖 接下来几篇文章要开启一个Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的 ...

  2. Java语法糖设计

    语法糖 Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的字节码或者特定的方式对这 ...

  3. java语法糖---枚举

    java语法糖---枚举   在JDK5.0中提供了大量的语法糖,例如:自动装箱拆箱.增强for循环.枚举.泛型等.所谓“语法糖”就是指提供更便利的语法供程序员使用,只是在编译器上做了手脚,却没有提供 ...

  4. Java语法糖(二)

    语法糖之四:内部类 内部类:顾名思义,在类的内部在定义一个类.内部类仅仅是编译时的概念,编译成字节码后,内部类会生成单独的Class文件. 四种:成员内部类.局部内部类.匿名内部类.静态内部类. 1. ...

  5. Java语法糖(一)

    概述 语法糖(Syntactic Sugar):主要作用是提高编码效率,减少编码出错的机会. 解语法糖发生在Java源码被编译成Class字节码的过程中,还原回简单的基础语法结构. 语法糖之一:泛型( ...

  6. JVM总结-Java语法糖与Java编译器

    自动装箱与自动拆箱 首先要提到的便是 Java 的自动装箱(auto-boxing)和自动拆箱(auto-unboxing). 我们知道,Java 语言拥有 8 个基本类型,每个基本类型都有对应的包装 ...

  7. 深入理解java虚拟机(十二) Java 语法糖背后的真相

    语法糖(Syntactic Sugar),也叫糖衣语法,是英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语.指的是,在计算机语言中添加某种语法,这些语法糖虽然不会对语言 ...

  8. Java语法糖4:内部类

    内部类 最后一个语法糖,讲讲内部类,内部类指的就是在一个类的内部再定义一个类. 内部类之所以也是语法糖,是因为它仅仅是一个编译时的概念,outer.java里面定义了一个内部类inner,一旦编译成功 ...

  9. 转:【深入Java虚拟机】之六:Java语法糖

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/18011009 语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家P ...

随机推荐

  1. 《TCP/IP 详解 卷1:协议》第 3 章:链路层

    在体系结构中,我们知道:链路层(或数据链路层)包含为共享相同介质的邻居建立连接的协议和方法,同时,设计链路层的目的是为 IP 模块发送和接受 IP 数据报,链路层可用于携带支持 IP 的辅助性协议,例 ...

  2. PYTHON-流程控制之if/while/for-练习

    # 1 练习题## 简述编译型与解释型语言的区别,且分别列出你知道的哪些语言属于编译型,哪些属于解释型# 编译型:C, 谷歌翻译,一次翻译后结果后重复使用# 解释型:Python, 同声传译,边执行边 ...

  3. PYTHON-迭代器,xxx生成式

    一 迭代器1 什么是迭代器 #迭代器即迭代的工具,那什么是迭代呢? #迭代是一个重复的过程,每次重复即一次迭代,并且每次迭代的结果都是下一次迭代的初始值 while True: #只是单纯地重复,因而 ...

  4. Android动画分类

    动画分类 View动画(补间动画).帧动画.属性动画 View动画(补间动画)包括:平移.旋转.缩放.透明度,View动画是一种渐近式动画 帧动画:图片切换动画 属性动画:通过动态改变对象的属性达到动 ...

  5. PLSQL developer 连接不上64位Oracle 解决办法

    在64位Windows7上安装Oracle后,用PLSQL developer去连接数据库出现报错: Could not load "……\bin\oci.dll" OCIDLL ...

  6. wpf 自定义属性的默认值

    public int MaxSelectCount { get { return (int)GetValue(MaxSelectCountProperty); } set { SetValue(Max ...

  7. Java中的钩子方法

    钩子方法是啥 钩子顾名思义就是用来挂东西的.那么要挂东西必须有个被挂的东西,要不就是铁环.要不就是墙的边沿.所以要能挂住东西必须要有个被勾住的铁环,要一个钩子.那么在java中也是同样的原理,你首先需 ...

  8. bzoj3224 splay板子

    开始学习新知识:splay——tree 是个板子题,学习splay可以看博客 https://blog.csdn.net/Clove_unique/article/details/50630280 # ...

  9. 性能测试十九:jmeter参数优化+排错

    一:参数优化 1,控制台取样间隔的设置,在jmeter/bin/jmeter.properties文件中修改 summariser.interval=10,默认为30s,最低可修改为6s 2,Jvm参 ...

  10. ERP发货系统的修改(四十三)

    产品添加批号后相应修改产品库存表中对应批次产品的数量: /// <summary> /// 产品添加批号后相应修改产品库存表中对应批次产品的数量 /// </summary> ...