JAVA语法糖和语法糖编译

首先,部分总结文字引用 简书作者:Eric新之助 。链接:https://www.jianshu.com/p/4de08deb6ba4

已获得授权

clipboard.png


先简单了解下定义

语法糖

语法糖(Syntactic Sugar),也叫糖衣语法,是英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语。指的是,在计算机语言中添加某种语法,这种语法能使程序员更方便的使用语言开发程序,同时增强程序代码的可读性,避免出错的机会。
几乎每种语言都提供语法糖,它只是编译器实现的一些小把戏罢了,编译期间以特定的字节码或者特定的方式对这些语法做一些处理,开发者就可以直接方便地使用了。这些语法糖虽然不会提供实质性的功能改进,但是它们或能提高性能、或能提升语法的严谨性、或能减少编码出错的机会。Java提供给了用户大量的语法糖,比如泛型、自动装箱/拆箱、foreach循环、变长参数、内部类、枚举类、断言、JAVA8新特性(lambda、stream、方法引用等)......

解语法糖

语法糖的存在主要是方便开发人员使用。但其实,Java 虚拟机并不支持这些语法糖,这些语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖。
说到编译,大家肯定都知道,Java 语言中,javac命令可以将后缀名为.java的源文件编译为后缀名为.class的可以运行于 Java 虚拟机的字节码。
如果你去看com.sun.tools.javac.main.JavaCompiler的源码,你会发现在compile()中有一个步骤就是调用desugar(),这个方法就是负责解语法糖的实现的。

学习语法糖原理最好的办法就是反编译看源码~

反编译工具:

IDEA默认反编译内置插件: JD-IntelliJ

对java8支持良好的反编译工具: procyon-decompiler

使用方法 : 
java -jar (jar包路径)\procyon-decompiler-0.5.30.jar(class文件路径)*.class

只支持到jdk1.5的反编译工具: jad

使用方法 : 
jad -o -8 -r -d(输出反编译文件路径) -sjava (class文件路径)

下面看看语法糖和三种反编译器编译后的代码

可变长度参数

可变参数由数组实现
Ps:可变长度参数必须作为方法参数列表中的的最后一个参数且方法参数列表中只能有一个可变长度参数

foreach循环原理

对于数组,foreach是用普通for循环实现的。
说明在对有实现Iterable接口的对象采用foreach语法糖的话,编译器会将这个for关键字转化为对目标的迭代器使用。
所以如果想要自己自定义的类可以采用foreach语法糖就要实现Iterable接口了。

自动装箱/拆箱

可以看到在自动装箱的时候,Java虚拟机会自动调用Integer的valueOf方法;
在自动拆箱的时候,Java虚拟机会自动调用Integer的intValue方法。这就是自动拆箱和自动装箱的原理
代码:

clipboard.png

IDEA反编译:

clipboard.png

procyon-decompiler反编译:

clipboard.png

jad反编译:

clipboard.png

泛型与类型擦除

对于java虚拟机来说,他根本不认识Map<Integer, String> map这样的语法。需要在编译阶段通过类型擦除的方式进行解语法糖。
类型擦除的主要过程如下:

将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。
移除所有的类型参数。

代码:

clipboard.png

IDEA反编译:

clipboard.png

procyon-decompiler反编译:

clipboard.png

jad反编译:

clipboard.png

泛型与重载

泛型编译出来的代码是会把类型擦除的,所以如下的代码是不能编译的,是因为参数List<Integer>和List<String>编译之后都被擦除了,变成了一样的原生类型List<E>,擦除动作导致这两个方法的特征签名变得一模一样,或者说两个一模一样的方法不能共存在一个class文件里

clipboard.png

那么如果加上返回类型呢?

clipboard.png

上面这段代码,IDE无法编译通过,javac编译可以通过。
网上找到一段引用:

在《Java虚拟机规范第二版》(JDK 1.5修改后的版本)的“§4.4.4
Signatures”章节及《Java语言规范第三版》的“§8.4.2 Method
Signature”章节中分别都定义了字节码层面的方法特征签名,以及Java代码层面的方法特征签名,特征签名最重要的任务就是作为方法独一无二不可重复的ID,在Java代码中的方法特征签名只包括了方法名称、参数顺序及参数类型,而在字节码中的特征签名还包括方法返回值及受查异常表。

根据上面的例子说明:由于List<String>和List<Integer>擦除后是同一个类型,只能添加两个并不需要实际使用到的返回值才能完成重载。这是否是一种引入泛型后的折中的解决方案呢?

枚举

Java枚举编译后实际上是生成了一个类,该类继承了 java.lang.Enum<E>,并添加了一个返回枚举数组的values()方法和valueOf()方法。
代码:

clipboard.png

IDEA反编译:

clipboard.png

procyon-decompiler反编译:

clipboard.png

jad反编译:

clipboard.png

内部类

Java的内部类也是一个语法糖,它仅仅是一个编译时的概念,outer.java里面定义了一个内部类inner,一旦编译成功,就会生成两个完全不同的.class文件了,分别是outer.class和outer$inner.class。所以内部类的名字完全可以和它的外部类名字相同。
代码:

clipboard.png

IDEA反编译:

clipboard.png

procyon-decompiler反编译:

clipboard.png

jad反编译:

Parsing /Users/dasouche/Downloads/product/springboot-demo/target/classes/com/example/demo/DemoOutClass.class...Parsing inner class /Users/dasouche/Downloads/product/springboot-demo/target/classes/com/example/demo/DemoOutClass$InnerClass.class... Generating /Users/dasouche/Desktop/jad158g.mac.intel/com/example/demo/DemoOutClass.java

clipboard.png

断言

代码:

clipboard.png

IDE反编译:

clipboard.png

procyon-decompiler反编译:

clipboard.png

jad反编译:

clipboard.png

JAVA8新特性中语法糖

Lambda表达式在Java 8中首先会生成一个私有的静态函数,这个私有的静态函数干的就是Lambda表达式里面的内容
代码:

clipboard.png

IDEA反编译:

clipboard.png

procyon-decompiler反编译:

clipboard.png

jad反编译 报错:

clipboard.png

clipboard.png

用javap反编译后:

clipboard.png

阅读 2.3k更新于 2018-08-17
推荐阅读
目录