Java 语言的语法糖
本文最后更新于:1 年前
Reference
环境和工具
- JDK 19
- Eclipse 2022-12 (v4.26.0)
- 反编译 Class 文件:
- CFR v0.152
只有这个可以反编译出 lambda 表达式的实现细节;没有 GUI,需要执行命令 java -jar cfr.jar <Class 文件>
- Jad v1.5.8g
没有 GUI,需要执行命令 jad.exe <Class 文件>
- Beyond Compare 4
- Java class to source、Java class to source tidied
Beyond Compare 反编译 Class 文件的插件,内部使用的是 Jad v1.5.8g
- Java class to source、Java class to source tidied
Luyten v0.5.4有 GUI;不能反编译出 lambda 表达式的实现细节
jd-gui-windows-1.6.6最高支持到 JDK 13
jad.exe + net.sf.jadclipse_3.3.0.jarEclipse 反编译 Class 文件的插件。无法反编译,Eclipse 试了 2019-09 v4.13.0,JDK 最低试到了 JDK 8 也不行
jd-eclipse-2.0.0Eclipse 反编译 Class 文件的插件。无法反编译,Eclipse 试了 2019-09 v4.13.0,JDK 最低试到了 JDK 8 也不行
- CFR v0.152
- 在线代码高亮显示工具
- WinHex v20.7
不能确定 JDK 版本的语法糖
+
SyntacticSugar\src\io\weichao\Plus.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\Plus.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 本质是使用了 StringBuilder。
Constant Folding 常量折叠
SyntacticSugar\src\io\weichao\ConstantFolding.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\ConstantFolding.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 常量直接连接到了一起。
JDK Alpha
JDK Beta
JDK 1.0
JDK 1.1
SUN SHIPS JDK 1.1 – JAVABEANS INCLUDED:
https://web.archive.org/web/20080210044125/http://www.sun.com/smi/Press/sunflash/1997-02/sunflash.970219.0001.xml
Inner Classes 内部类
成员内部类
SyntacticSugar\src\io\weichao\InnerClasses.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\InnerClasses.class
和 SyntacticSugar\bin\io\weichao\InnerClasses$Printer.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 内部类的构造方法的参数是外部类对象,所以内部类对象会隐式持有外部类对象的引用
这会影响外部类对象的回收
。
局部内部类
定义在一个方法或者一个作用域里面的类,局部内部类的访问仅限于该方法内或者该作用域内。
SyntacticSugar\src\io\weichao\InnerClasses2.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\InnerClasses2.class
和 SyntacticSugar\bin\io\weichao\InnerClasses2$1$Printer.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 内部类的构造方法的参数是外部类对象,所以内部类对象会隐式持有外部类对象的引用
这会影响外部类对象的回收
。
匿名内部类
SyntacticSugar\src\io\weichao\InnerClasses3.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\InnerClasses3.class
、SyntacticSugar\bin\io\weichao\InnerClasses3$1.class
、 SyntacticSugar\bin\io\weichao\IWorker.class
和 SyntacticSugar\bin\io\weichao\Printer.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 内部类的构造方法的参数是外部类对象,所以内部类对象会隐式持有外部类对象的引用
这会影响外部类对象的回收
。
静态内部类
使用 static 关键字修饰的内部类。
SyntacticSugar\src\io\weichao\InnerClasses4.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\InnerClasses4.class
和 SyntacticSugar\bin\io\weichao\InnerClasses4$Printer.class
反编译使用的命令:
1 |
|
反编译后:
- 静态内部类不需要依赖外部类。
问题
局部内部类和匿名内部类只能访问外部类的局部 final 变量。
SyntacticSugar\src\io\weichao\InnerClassesQ.java
1 |
|
正向分析:
【存在的问题】可能因为变量的生命周期导致变量不可访问。
【解决问题】复制变量:如果变量的值在编译期可以确定,就在常量池中生成一个值给内部类用;如果不能确定,就将变量的值传给内部类通过构造方法传参
。
- 【引入问题】如果内部类修改了传进来的复制的值,无法同步给外部类原来的变量。
【解决问题】将变量使用 final 修饰,不允许修改对于引用类型的变量,是不允许指向新的对象
。
编译后生成:SyntacticSugar\bin\io\weichao\InnerClassesQ.class
和 SyntacticSugar\bin\io\weichao\InnerClassesQ$1.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 内部类的构造方法的参数
第 1 个
是外部类对象,所以内部类对象会隐式持有外部类对象的引用这会影响外部类对象的回收
。 - 去掉了变量的 final 修饰符;对于确定的值
world
直接使用字面量,对于不确定的值hello
使用参数第 2 个
传递的值。
Serialization 序列化、持久化
- Java 提供了一种对象序列化的机制:一个对象可以被表示为一个字节序列
包括该对象的数据、对象类型和存储在该对象中的数据的类型
。 - 反序列化:可以通过读取写入了序列化对象的文件,在内存中重建对象。
- 序列化和反序列化可以跨平台
完全由 Java 虚拟机实现。
。
SyntacticSugar\src\io\weichao\Printer.java
1 |
|
SyntacticSugar\src\io\weichao\Serialization.java
1 |
|
使用 WinHex 打开 printer.ser:
序列化生成的文件格式[1]解析:
十六进制 | 描述 |
---|---|
0xACED | 魔数 |
0x0005 | 版本号:5 |
0x73 | 接下来读取到的将是一个对象 |
0x72 | 该对象是一个对类的描述 |
0x0012 | 长度:18 |
0x696F2E7765696368616F2E5072696E746572 | io.weichao.Printer |
0xC778DAF642AECC68 = -4073265111741051800 | serialVersionUID 的值 |
0x02 | 标识类版本:2 |
0x0001 | 字段个数:1 |
0x4C | 类型是对象 |
0x0004 | 长度:4 |
0x74657874 | text |
0x74 | String 类型 |
0x0012 | 长度:18 |
0x4C | 类型是对象 |
0x6A6176612F6C616E672F537472696E673B78 | java/lang/String; |
0x78 | 结束 |
0x70 | 没有超类 |
0x74 | String 类型 |
0x0005 | 长度:5 |
0x68656C6C6F | hello |
SyntacticSugar\src\io\weichao\Deserialization.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\Printer.class
、SyntacticSugar\bin\io\weichao\Serialization.class
和 SyntacticSugar\bin\io\weichao\Deserialization.class
TODO 反编译后和 java 文件基本一致,所以序列化应该不算一个语法糖?
通过反编译可以看到:
- transient 修饰的变量不会被序列化。
- 类属性的 setter() 和 getter() 方法对序列化没有影响,也不在序列化文件中体现。
J2SE 1.2
SUN DELIVERS NEXT VERSION OF THE JAVA PLATFORM —— Java™ 2 Brand Unveiled:
https://web.archive.org/web/20070816170028/http://www.sun.com/smi/Press/sunflash/1998-12/sunflash.981208.9.xml
strictfp keyword strictfp 关键字
by JVM 17 an obsolete keyword, shouldn’t be used in new code
J2SE 1.3
Java 2 SDK, Standard Edition, version 1.3 —— Summary of New Features and Enhancements:
https://web.archive.org/web/20061107051825/https://java.sun.com/j2se/1.3/docs/relnotes/features.html
J2SE 1.4
JavaTM 2 SDK, Standard Edition, version 1.4.2 —— Summary of New Features and Enhancements:
https://web.archive.org/web/20070108015942/https://java.sun.com/j2se/1.4.2/docs/relnotes/features.html
Assertion Facility 断言
- 可以快速揭示程序员对程序行为的误解。
- Eclipse 开启断言:添加 VM arguments
-ea
。
SyntacticSugar\src\io\weichao\Assertion.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\Assertion.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 本质是 if 语句。如果断言结果为 true,则什么都不做,程序继续执行;如果断言结果为 false,则程序抛出 AssertError 来打断程序的执行。
Java SE 5
New Features and Enhancements —— J2SE 5.0:
https://docs.oracle.com/javase/1.5.0/docs/relnotes/features.html
Generics 泛型
- 允许类型或方法对各种类型的对象进行操作,同时提供了编译时类型安全检测机制
该机制允许程序员在编译时检测到非法的类型
。 - 为集合框架添加了编译时类型安全性,且有自动装箱/拆箱。
- 泛型的本质是参数化类型
所操作的数据类型被指定为一个参数
。
泛型方法
1、无界的类型参数SyntacticSugar\src\io\weichao\Generics.java
1 |
|
编译后生成:SyntacticSugar\src\io\weichao\Generics.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 【类型擦除】方法参数的类型被替换为
Object
。
2、有界的被允许传递到一个类型参数的类型种类范围
类型参数SyntacticSugar\src\io\weichao\Generics2.java
1 |
|
编译后生成:SyntacticSugar\src\io\weichao\Generics2.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 【类型擦除】方法参数的类型被替换为上界
Comparable
。
泛型类
SyntacticSugar\src\io\weichao\Generics3.java
1 |
|
编译后生成:SyntacticSugar\src\io\weichao\Generics3.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 【类型擦除】方法参数的类型被替换为
Object
。
Enhanced for Loop foreach、增强 for 循环
消除了迭代器和索引变量在迭代集合和数组时的繁琐和易错性。
SyntacticSugar\src\io\weichao\EnhancedForLoop.java
1 |
|
编译后生成:SyntacticSugar\src\io\weichao\EnhancedForLoop.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 本质是普通的 for 循环。
Autoboxing/Unboxing 自动装箱/拆箱
不需要手动转换原始类型如 int
和包装类型如 Integer
。
SyntacticSugar\src\io\weichao\Autoboxing.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\Autoboxing.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- int 被自动装箱成 Integer,Integer 被自动拆箱成 int。
Typesafe Enums 枚举
创建具有任意方法和字段的枚举类型。
SyntacticSugar\src\io\weichao\Enums.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\Color.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 本质是一个继承
Enum
的类,修饰符是 final。 - 枚举元素的修饰符是 public static final。
- 创建了一个数组
ENUM$VALUES[]
,修饰符是 private static final。 - 构造方法有两个参数:String 类型的枚举元素名、int 类型的索引。
- 在静态代码块中,通过构造方法给枚举元素设置索引,并加入数组
ENUM$VALUES[]
。 - 当调用
values()
方法时,会使用System.arraycopy()
方法复制ENUM$VALUES[]
到一个新数组,并返回新数组。 - 当调用
valueOf()
方法时,会调用Enum.valueOf()
方法返回枚举元素。
Varargs 可变参数、变长参数
不需要手动将参数列表装箱到数组中。
SyntacticSugar\src\io\weichao\Varargs.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\Varargs.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 本质是数组。
Static Import 静态导入
不需要使用类名限定静态成员。
Annotations(Metadata) 注解(元数据)
使用工具从源代码中的注解生成样板代码声明式编程风格
。
SyntacticSugar\src\io\weichao\Annotations.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\Annotations.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 本质是一个继承
Annotation
的接口。
Java SE 6
Enhancements in Java SE 6:
https://docs.oracle.com/javase/8/docs/technotes/guides/language/enhancements.html
Java SE 7 (LTS 2011.07~2019.07、2022.07)
Enhancements in Java SE 7:
https://docs.oracle.com/javase/8/docs/technotes/guides/language/enhancements.html
Binary Literals 二进制字面量
整数类型 byte、short、int 和 long
也可以使用二进制数字表示。
SyntacticSugar\src\io\weichao\BinaryLiterals.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\BinaryLiterals.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 二进制转成了十进制。
Underscores in Numeric Literals 数字字面量中的下划线
任意数量的 _
可以出现在数字字面量中数字之间的任何位置。
SyntacticSugar\src\io\weichao\UnderscoresInNumericLiterals.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\UnderscoresInNumericLiterals.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 去掉了下划线。
Strings in switch Statements switch 语句中的字符串
可以在 switch 语句的表达式中使用 String 类。
SyntacticSugar\src\io\weichao\StringsInSwitchStatements.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\StringsInSwitchStatements.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 先使用
hashCode()
方法计算字符串的哈希值,通过哈希值查找 case,再使用equals()
方法比较避免哈希碰撞导致的错误
。
Type Inference for Generic Instance Creation 泛型实例创建的类型推断
使用 <>
替换调用泛型类的构造函数所需的类型参数。
Improved Compiler Warnings and Errors When Using Non-Reifiable Formal Parameters with Varargs Methods
使用编译器选项 -Xlint:varargs
以及注释 @SafeVarargs
和 @SuppressWarnings({"unchecked", "varargs"})
可以关闭编译器的警告。
The try-with-resources Statement try-with-resources 语句
在语句结束时会自动关闭资源任何实现新的 java.lang.AutoCloseable 接口或 java.io.Closeable 接口的对象都可以用作资源
。
SyntacticSugar\src\io\weichao\TryWithResources.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\TryWithResources.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 添加了 finally 代码块,关闭缓冲流。
Catching Multiple Exception Types and Rethrowing Exceptions with Improved Type Checking 捕获多种异常类型并通过改进的类型检查重新抛出异常
- 单个 catch 块可以处理不止一种类型的异常。
- 可以在方法声明的 throws 子句中指定更具体的异常类型。
Java SE 8 (LTS 2014.03~2022.03、2030.12)
Enhancements in Java SE 8:
https://docs.oracle.com/javase/8/docs/technotes/guides/language/enhancements.html
Lambda Expressions Lambda 表达式、闭包
[2][3]
允许把一个方法作为另一个方法的参数,而不需要一个含有方法的类的对象或一个含有方法的接口实现类的对象。
Method references 方法引用
SyntacticSugar\src\io\weichao\LambdaExpressions.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\LambdaExpressions.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 本质是调用 LambdaMetafactory.metafactory() 方法。
Default methods 默认方法
接口允许拥有带有方法实现的默认方法,在方法前使用关键字 default 来区别于普通的抽象方法。
New and Enhanced APIs That Take Advantage of Lambda Expressions and Streams in Java SE 8
新的和增强的 APIs——借助 Java SE 8 中的 Lambda 表达式和流。
Improved Type Inference
可以利用目标类型来推断泛型方法调用的类型参数。
Annotations on Java Types
可以在任何使用类型的地方应用注解。
Repeating Annotations 重复注解
允许在同一声明类型(类,属性,或方法)上多次使用同一个注解。
SyntacticSugar\src\io\weichao\FieldMap.java
1 |
|
SyntacticSugar\src\io\weichao\FieldMaps.java
1 |
|
SyntacticSugar\src\io\weichao\RepeatingAnnotations.java
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\FieldMap.class
、SyntacticSugar\bin\io\weichao\FieldMaps.class
和 SyntacticSugar\bin\io\weichao\RepeatingAnnotations.class
反编译使用的命令:
1 |
|
反编译后:
解语法糖:
- 对于重复注解,表面上使用的是 FieldMap,本质上使用的是 FieldMaps
FieldMap[]
;当非重复时,使用的还是 FieldMap。
Method parameter reflection 使用反射获取方法参数名
可以使用 java.lang.reflect.Executable.getParameters() 方法获取任何方法或构造方法的形参名称默认情况下,Class 文件不存储形式参数名称,需要使用 javac 编译器的 -parameters 选项编译源文件
。
Java SE 9
Java Language Changes for Java SE 9:
https://docs.oracle.com/en/java/javase/19/language/java-language-changes.html#GUID-B06D7006-D9F4-42F8-AD21-BF861747EDCF
More Concise try-with-resources Statements 更简洁的 try-with-resources 语句
@SafeVarargs Annotation Allowed on Private Instance Methods
@SafeVarargs 注解现在被允许给私有实例方法使用了。
Diamond Syntax and Anonymous Inner Classes
菱形运算符 <>
现在被允许给匿名内部类使用了。
Underscore Character Not Legal Name
如果您使用下划线字符 _
作为标识符,您的源代码将无法再被编译。
Support for Private Interface Methods 支持私有接口方法
Java SE 10
Java Language Changes for Java SE 10:
https://docs.oracle.com/en/java/javase/19/language/java-language-changes.html#GUID-7D5FDD65-ACE4-4B3C-80F4-CC01CBD211A4
Local Variable Type Inference 局部变量类型推断
SyntacticSugar\src\io\weichao\LocalVariableTypeInference.java
1 |
|
反编译使用的命令:
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\LocalVariableTypeInference.class
反编译后:
解语法糖:
- 变量类型被还原回原本的类型。
Java SE 11 (LTS 2018.09~2023.09、2026.09)
Java Language Changes for Java SE 11:
https://docs.oracle.com/en/java/javase/19/language/java-language-changes.html#GUID-8693D1DF-3DE3-4588-ABA8-397C65B4169A
Java SE 12
Java Language Changes for Java SE 12:
https://docs.oracle.com/en/java/javase/19/language/java-language-changes.html#GUID-B01F389D-5DFE-4A3F-818E-99478DC642CE
Switch Expressions switch 表达式
新型 case 标签具有以下形式:
1 |
|
Java SE 13
Java Language Changes for Java SE 13:
https://docs.oracle.com/en/java/javase/19/language/java-language-changes.html#GUID-763949E4-627B-462F-AC7B-54DEA39E5989
Text Blocks 文本块
SyntacticSugar\src\io\weichao\TextBlocks.java
1 |
|
反编译使用的命令:
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\TextBlocks.class
反编译后:
解语法糖:
- 本质是
\n
。
Switch Expressions switch 表达式
可以在 case 中使用 yield 语句返回需要的值了。
Java SE 14
Java Language Changes for Java SE 14:
https://docs.oracle.com/en/java/javase/19/language/java-language-changes.html#GUID-8FD2B5E3-46C7-4C6C-8E8A-64AB49ABF855
Pattern Matching for the instanceof Operator instanceof 的模式匹配
你可以指定一个绑定变量;如果 instanceof 运算符的结果是 true,则将被测试的对象分配给绑定变量。
Records 记录
与 enum 一样,记录是类的一种受限形式。它是普通数据载体的理想选择,这些类包含不打算更改的数据并且仅包含构造方法和访问器等最基本的方法。
SyntacticSugar\src\io\weichao\Records.java
1 |
|
反编译使用的命令:
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\Records.class
反编译后:
解语法糖:
- 本质是一个继承 Record 类的类,修饰符是 final。
- 方法属性的修饰符是 final,有和方法属性同名的方法用于获取方法属性的值。
- 有带参的构造方法。
- 有 toString()、hashCode()、equals() 方法。
Java SE 15
Java Language Changes for Java SE 15:
https://docs.oracle.com/en/java/javase/19/language/java-language-changes.html#GUID-486A76FF-9A42-44AA-9668-5B53AA4AB2D2
Sealed Classes 密封类
可以控制哪些类可以对定义的类进行扩展。
Java SE 16
Java Language Changes for Java SE 16:
https://docs.oracle.com/en/java/javase/19/language/java-language-changes.html#GUID-EE327F97-BF08-46D2-8426-085CBE585797
Java SE 17 (LTS 2021.09~2026.09、2029.09)
Java Language Changes for Java SE 17:
https://docs.oracle.com/en/java/javase/19/language/java-language-changes.html#GUID-67ED83E7-D79F-4F46-AA33-41031E5CD094
Pattern Matching for switch Expressions and Statements switch 的模式匹配
在早期版本中,选择器表达式的计算结果必须为数字、字符串或 enum 常量,并且 case 标签必须是常量。但是,在此版本中,选择器表达式可以是任何类型,并且 case 标签可以具有模式。
SyntacticSugar\src\io\weichao\SwitchPatternMatching.java
1 |
|
反编译使用的命令:
1 |
|
编译后生成:SyntacticSugar\bin\io\weichao\SwitchPatternMatching.class
反编译后:
解语法糖:
- 调用 SwitchBootstraps.typeSwitch() 方法,计算出数组中匹配的元素的索引
null 是 -1
。
Java SE 18
Java Language Changes for Java SE 18:
https://docs.oracle.com/en/java/javase/19/language/java-language-changes.html#GUID-7A599857-71A8-42C9-8F18-B9F30B03C059
Pattern Matching for switch Expressions and Statements switch 表达式
- 常量的 case 标签必须放在受保护模式 case 标签之前。
- 受保护模式 case 标签必须放在非受保护模式 case 标签之前。
Java SE 19
Java Language Changes for Java SE 19:
https://docs.oracle.com/en/java/javase/19/language/java-language-changes.html#GUID-1D8E4E08-A03C-4462-9801-26152159EE55
Pattern Matching for switch Expressions and Statements switch 表达式
- 受保护模式标签
switch 的 case 标签的模式后添加 when 语句
。 - 如果选择器表达式的计算结果为
null
并且 switch 没有null
case 标签,则抛出 NullPointerException。 - 如果 switch 在编译时是详尽的但是在运行时不是,则抛出 MatchException。
Record Patterns 记录的模式
Java SE 20
Java SE 21 (LTS 2023.09~2028.09、2031.09)
参考
- Java Object Serialization Specification — 6.4 Grammar for the Stream Format:https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serialTOC.html ↩
- Java Lambda 表达式:https://www.runoob.com/java/java8-lambda-expressions.html ↩
- Java8——Lambda表达式、方法引用、默认方法的详细介绍与使用案例【一万字】:https://juejin.cn/post/6987569747848232996 ↩