0%

Java语法糖和Java编译器

Java语法和Java字节码之间的差异,都是通过Java编译器来协调的。

自动装箱与自动拆箱

自动装箱(auto-boxing)与自动拆箱(auto-unboxing)。

Java语言拥有8个基本类型,每个基本类型都有对应的包装(wrapper)类型。之所以需要包装类型,是因为许多Java核心类库的API都是面向对象的。如Java核心类库中的容器类,就只支持引用类型。

当需要一个能够存储数值的容器类时,往往定义一个存储包装类对象的容器。对于基本类型的数值来说,需要先将其转换为对应的包装类,再存图容器之中。在Java程序中,这个转换可以是显式,也可以是隐式的,后者正是Java中的自动装箱。

泛型与类型擦除

示例中生成的字节码,往ArrayList中添加元素的add方法,所接受的参数类型是Object;而从ArrayList中获取元素的get方法,其返回类型也是Object。

后者返回类型,在字节码中需要进行向下转换,将所返回的Object强制转换为Integer,方能进行接下来的自动拆箱。

之所以出现这种情况,是因为Java泛型的类型擦除:Java程序里的泛型信息,在JVM里全部都丢失了,这么做是为了兼容引入泛型之前的代码。

但并不是每一个泛型参数被擦除类型后都会变成Object类。对于限定了继承类的泛型参数,经过类型擦除后,所有的泛型类型都将变成所限定的继承类。Java编译器将选取该泛型所能指代的所有类中层次最高的那个,作为替换泛型的类。

泛型会被类型擦除,但依然必要。Java编译器可以根据泛型参数判断程序中的语法是否正确。尽管经过类型擦除后,ArrayList.add方法所接收的参数是Object类型,但是往泛型参数为Integer类型的ArrayList中添加字符串对象,Java编译器是会报错的。

桥接方法

参数类型不同

泛型的类型擦除带来了不少问题。其中一个是方法重写。但通过桥接方法可以解决参数类型不匹配问题。

返回类型不同

如果子类定义了一个与父类参数类型相同的方法,其返回类型为父类方法返回类型的子类,那么Java编译器也会为其生成桥接方法。

class文件里允许出现两个同名、同参数类型但是不同返回类型的方法。原方法与桥接方法便是例子。

其他语法糖

变长参数
try-with-resources以及在同一catch代码块中捕获多种异常等语法糖。

foreach循环允许Java程序在for循环里遍历数组或者Iterable对象。

foreach循环数组

对于数组来说,foreach循环将从0开始逐一访问数组中的元素,直至数组的末尾。

1
2
3
4
5
6
7
8
9
10
11
12
13
public void foo(int[] array) {
for(int item : array) {

}
}

public void bar(int[] array) {
int[] myArray = array;
int length = myArray.length;
for(int i=0; i < length; i++) {
int item = myArray[i];
}
}

foreach循环Iterator

对于Iterable对象来说,foreach循环将调用其iterator方法,并且用它的hasNext以及next方法来遍历该Iterable对象中的元素。

1
2
3
4
5
6
7
8
9
10
11
public void foo(ArrayList<Integer> list) {
for(Integer item : list) {
}
}

public void bar(ArrayList<Integer> list) {
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer item = iterator.next();
}
}

字符串switch

字符串switch编译而成的字节码看起来非常复杂,但实际上就是一个哈希桶。由于每个case所截获的字符串都是常量值,因此,Java编译器会将原来的字符串switch转换为int值switch,比较所输入的字符串的哈希值。

由于字符串哈希值很容易发生碰撞,因此,还需要用String.equals逐个比较相同哈希值的字符串。

总结

基本类型和其包装类型之间的自动转换,也就是自动装箱、自动拆箱,是通过加入[Wrapper].valueOf(如Integer.valueOf)以及[Wrapper].[primitive]Value(如Integer.intValue)方法调用来实现的。

Java程序中的泛型信息会被擦除。Java编译器将选取该泛型所能指代的所有类中层次最高的那个,作为替换泛型的具体类。

由于Java语义与Java字节码中关于重写的定义并不一致,因此Java编译器会生成桥接方法作为适配器。