编译期语法糖(一)
所谓的语法糖,就是指java编译器在把.java文件编译成.class字节码的过程中,自动生成和转换的一些代码.主要是为了减轻程序猿的负担.
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。
默认构造器
我们先创建一个没有默认构造器的类
1 | public class Demo04_1 { |
使用javap命令反编译,得到如下字节码:
1 | { |
可以看出,编译期自动生成了一个无参的构造器,引用了父类,也就是Object的init 方法.
就相当于下面代码:
1 | public class Demo04_1 { |
自动拆装箱
在Java SE5中,为了减少开发人员的工作,Java提供了自动拆箱与自动装箱功能。
自动装箱: 就是将基本数据类型自动转换成对应的包装类。
自动拆箱:就是将包装类自动转换成对应的基本数据类型。
例如下面代码:
1 | public class Demo04_2 { |
我们同样适用javap反编译,然后查看main方法中的code
1 | public static void main(java.lang.String[]); |
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
查看字节码,可以看出是同步Integer.valueOf()进行装箱
6: invokevirtual #3 // Method java/lang/Integer.intValue:()I
通过Integer.inValue()进行拆箱
将字节码转换为java代码如下:
1 | public class Demo04_2 { |
泛型擦除
在Java字节码中是没有泛型这一概念, 只有普通方法和普通类, 所有泛型类的泛型参数都会在编译时期被擦除, 所以泛型类并没有自己独有的Class类对象比如List
我们先来看一段代码:
1 | public class Demo04_3 { |
同样进行反编译,然后查看main方法的字节码:
1 | Code: |
22: invokeinterface #6, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
27: checkcast #7 // class java/lang/Integer
通过这两行可以看出,调用list.get()返回结果为Object.
然后通过类型转换指令 checkcast 转换成Integer对象.
相当于:
1 | Integer a = (Integer) list.get(0); |
虽然在编译期泛型在code代码中擦除,但是我们却可以在字节码的其他地方获得这些信息
1 | LocalVariableTypeTable: |
这就是一个泛型信息表,表示槽位1中的list的泛型为Ljava/lang/Integer.
泛型擦除可能引起的问题
我们来看下面一段代码:
1 | public class Demo04_3 { |
这是,在编译期是不报错的,好想是正确执行下面的类型转换
List<String> list2 = (List<String>)map.get("list");
但是在运行时,却会报出
1 | java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String |
这是由于,在编译期,进行了泛型擦除,而再get后进行类型转换时,jvm也没有(List
可变参数
先看下面一段代码:
1 | public class Demo04_4 { |
通过Javap反编译
我们先看foo方法的字节码:
1 | public static void foo(java.lang.String...); |
1 |
|
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=1, args_size=1
0: iconst_2
1: anewarray #4 // class java/lang/String
4: dup
5: iconst_0
6: ldc #5 // String Hello
8: aastore
9: dup
10: iconst_1
11: ldc #6 // String World
13: aastore
14: invokestatic #7 // Method foo:([Ljava/lang/String;)V
17: return
1 |
|
可以看出是新new了一个数组,当做可变参数传给了foo
也就是,编译器将上述代码转换为
1 | public class Demo04_4 { |
注意: 如果调用foo() ,则等价代码为 foo(new String[]{}); ,创建了一个空数组,而不会传递Null过去.