程序员社区

JVM第五卷---编译期处理

JVM第五卷---编译期处理

  • 编译期处理
    • 默认构造器
    • 自动拆装箱
    • 泛型集合取值--泛型擦除
    • 可变参数
    • foreach 循环
    • switch 字符串
    • switch 枚举
    • 枚举类
    • try-with-resources
    • 方法重写时的桥接方法
    • 匿名内部类
  • 插入式注解处理器

编译期处理

所谓的 语法糖 ,其实就是指 java 编译器把 *.java 源码编译为 *.class 字节码的过程中,自动生成
和转换的一些代码,主要是为了减轻程序员的负担,算是 java 编译器给我们的一个额外福利(给糖吃
嘛)

注意,以下代码的分析,借助了 javap 工具,idea 的反编译功能,idea 插件 jclasslib 等工具。

另外,编译器转换的结果直接就是 class 字节码,只是为了便于阅读,给出了 几乎等价 的 java 源码方式,并不是编译器还会转换出中间的 java 源码,切记。

默认构造器

public class Candy1 { }

编译成class后的代码

在这里插入图片描述


自动拆装箱

这个特性是 JDK 5 开始加入的, 代码片段1 :

在这里插入图片描述

这段代码在 JDK 5 之前是无法编译通过的,必须改写为 代码片段2 :

在这里插入图片描述

显然之前版本的代码太麻烦了,需要在基本类型和包装类型之间来回转换(尤其是集合类中操作的都是包装类型),因此这些转换的事情在 JDK 5 以后都由编译器在编译阶段完成。即 代码片段1 都会在编译阶段被转换为 代码片段2


泛型集合取值–泛型擦除

泛型也是在 JDK 5 开始加入的特性,但 java 在编译泛型代码后会执行 泛型擦除 的动作,即泛型信息在编译为字节码之后就丢失了,实际的类型都当做了 Object 类型来处理:

在这里插入图片描述
所以在取值时,编译器真正生成的字节码中,还要额外做一个类型转换的操作:

在这里插入图片描述
如果前面的 x 变量类型修改为 int 基本类型那么最终生成的字节码是:

在这里插入图片描述
还好这些麻烦事都不用自己做。

擦除的是字节码上的泛型信息,可以看到 LocalVariableTypeTable 仍然保留了方法参数泛型的信息

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

局部变量没有办法通过反射的方式,拿到泛型信息,只有在方法的参数和返回值上带的泛型信息才可以通过反射获取到

使用反射,仍然能够获得这些信息:

在这里插入图片描述

在这里插入图片描述
输出
在这里插入图片描述


可变参数

可变参数也是 JDK 5 开始加入的新特性:

例如:

在这里插入图片描述
可变参数 String… args 其实是一个 String[] args ,从代码中的赋值语句中就可以看出来。同样 java 编译器会在编译期间将上述代码变换为:

在这里插入图片描述
注意

如果调用了 foo() 则等价代码为 foo(new String[]{})创建了一个空的数组,而不会传递null 进去


foreach 循环

仍是 JDK 5 开始引入的语法糖,数组的循环:

在这里插入图片描述
会被编译器转换为:

在这里插入图片描述
而集合的循环:
在这里插入图片描述
实际被编译器转换为对迭代器的调用:
在这里插入图片描述
注意

foreach 循环写法,能够配合数组,以及所有实现了 Iterable 接口的集合类一起使用,其中Iterable 用来获取集合的迭代器( Iterator )


switch 字符串

从 JDK 7 开始,switch 可以作用于字符串和枚举类,这个功能其实也是语法糖,例如:

在这里插入图片描述
注意

switch 配合 String 和枚举使用时,变量不能为null,原因分析完语法糖转换后的代码应当自然清楚

会被编译器转换为:

在这里插入图片描述
可以看到,执行了两遍 switch,第一遍是根据字符串的 hashCode 和 equals 将字符串的转换为相应byte 类型,第二遍才是利用 byte 执行进行比较。

为什么第一遍时必须既比较 hashCode,又利用 equals 比较呢

hashCode 是为了提高效率,减少可能的比较;而 equals 是为了防止 hashCode 冲突例如 BM 和 C. 这两个字符串的hashCode值都是2123 ,如果有如下代码:

在这里插入图片描述
会被编译器转换为:

在这里插入图片描述


switch 枚举

switch 枚举的例子,原始代码:

在这里插入图片描述
转换后代码:

在这里插入图片描述


枚举类

JDK 7 新增了枚举类,以前面的性别枚举为例:

enum Sex { MALE, FEMALE }

转换后代码:

在这里插入图片描述


try-with-resources

JDK 7 开始新增了对需要关闭的资源处理的特殊语法 try-with-resources:`

在这里插入图片描述
其中资源对象需要实现 AutoCloseable 接口,例如 InputStream 、 OutputStream 、 Connection 、 Statement 、 ResultSet 等接口都实现了 AutoCloseable ,使用 try-with- resources 可以不用写 finally 语句块,编译器会帮助生成关闭资源代码,例如:

在这里插入图片描述
会被转换为:

在这里插入图片描述
为什么要设计一个 addSuppressed(Throwable e) (添加被压制异常)的方法呢?是为了防止异常信息的丢失(想想 try-with-resources 生成的 fianlly 中如果抛出了异常):

在这里插入图片描述
输出:

在这里插入图片描述
如以上代码所示,两个异常信息都不会丢


方法重写时的桥接方法

我们都知道,方法重写时对返回值分两种情况:

  • 父子类的返回值完全一致
  • 子类返回值可以是父类返回值的子类(比较绕口,见下面的例子)

在这里插入图片描述
对于子类,java 编译器会做如下处理:

在这里插入图片描述
其中桥接方法比较特殊,仅对 java 虚拟机可见,并且与原来的 public Integer m() 没有命名冲突,可以用下面反射代码来验证:

for (Method m : B.class.getDeclaredMethods()) 
{ System.out.println(m); }

会输出:

public java.lang.Integer test.candy.B.m() 
public java.lang.Number test.candy.B.m()

匿名内部类

源代码:

在这里插入图片描述
转换后代码:

在这里插入图片描述
在这里插入图片描述
引用局部变量的匿名内部类,源代码:

在这里插入图片描述
转换后代码:
在这里插入图片描述
注意

这同时解释了为什么匿名内部类引用局部变量时,局部变量必须是 final 的:因为在创建Candy11$1 对象时,将 x 的值赋值给了 Candy11$1 对象的 val$x 属性,所以 x 不应该再发生变化了,如果变化,那么 val$x 属性没有机会再跟着一起变化


插入式注解处理器

插入式注解处理器可以看做是一组编译器插件,当这些插件工作的时候,可以读取,修改和添加抽象语法树中任意的元素。

如果这些插件在处理注解期间对语法树进行过修改,编译器将会到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止,每一次循环称为一个轮次。

著名的Lombok就是通过插入式注解处理器实现的。

打包自定义插入式注解: https://www.cnblogs.com/avenwu/p/4173899.html
获取类、字段:https://blog.csdn.net/zhuhai__yizhi/article/details/51394810
编辑语法树:https://blog.csdn.net/a_zhenzhen/article/details/86065063
https://www.cnblogs.com/kanyun/p/11541826.html
生成 GET / SET 方法:https://www.jianshu.com/p/68fcbc154c2f

插入式注解-自动生成 Get / Set

赞(0) 打赏
未经允许不得转载:IDEA激活码 » JVM第五卷---编译期处理

相关推荐

  • 暂无文章

一个分享Java & Python知识的社区