JDK 14是由JSR 389在Java社区推进中指定的Java SE平台版本14的开源参考实现。已于2020年3月17日正式发布。此版本所包含的JEP(Java增强提案,Java Enhancement Propostal),共16个新特性。其中包含两个孵化器模块(Incubator)、三个预览特性(Preview)、两个弃用功能(Deprecate)和两个删除功能(Remove)。
JDK 14新增的16个新特性,有部分与开发者日常编程息息相关,该部分新特性在如下表格中通过标红进行标识,本文主要对标红的JEP进行讲解,表格如下:
NO | Title | 说明 |
---|---|---|
305 | Pattern Matching for instanceof (Preview) | instanceof 的模式匹配(预览) |
343 | Packaging Tool (Incubator) | 打包工具(孵化) |
345 | NUMA-Aware Memory Allocation for G1 | G1 的NUMA 内存分配优化 |
349 | JFR Event Streaming | JFR事件流 |
352 | Non-Volatile Mapped Byte Buffers | 非原子性的字节缓冲区映射 |
358 | Helpful NullPointerExceptions | 非常有帮助的空指针异常 |
359 | Records (Preview) | Records(预览) |
361 | Switch Expressions (Standard) | Switch 表达式(标准) |
362 | Deprecate the Solaris and SPARC Ports | 弃用 Solaris 和S PARC 端口 |
363 | Remove the Concurrent Mark Sweep (CMS) Garbage Collector | 移除 CMS(Concurrent Mark Sweep)垃圾收集器 |
364 | ZGC on macOS | macOS 系统上的 ZGC |
365 | ZGC on Windows | Windows 系统上的 ZGC |
366 | Deprecate the ParallelScavenge + SerialOld GC Combination | 弃用 ParallelScavenge + SerialOld GC 组合 |
367 | Remove the Pack200 Tools and API | 移除Pack200 Tools和API |
368 | Text Blocks (Second Preview) | 文本块(第二个预览版) |
370 | Foreign-Memory Access API (Incubator) | 外部存储器API(孵化) |
instanceof 的模式匹配。使用匹配模式增强Java编程语言中的instanceof操作符。模式匹配允许程序中的共有的逻辑(即从对象中有条件地提取组件)更简洁、更安全地表达。这是JDK 14中的一个预览语言特性。
老版本使用姿势
/**
* 老版本的instanceof判断后,须显示强转
*/
@Test
public void testOld() {
Object strObj = "Hello Instanceof!";
if (strObj instanceof String) {
// 显示强转
String str = (String) strObj;
System.out.println(str.contains("Hello"));
}
}
新版本使用姿势
/**
* 新版本的instanceof的使用,无须显示强转
*/
@Test
public void testNew() {
Object strObj = "Hello Instanceof!";
if (strObj instanceof String str) {
// 无需显示的进行强转
System.out.println(str.contains("Hello"));
}
}
非常有帮助的空指针异常。通过精确描述哪个变量为空,提高JVM生成的NEP(NullPointerExceptions)的可用性。Java14之前,NEP报错信息不会指出为Null的实例具体是那一个。例如:a.b.c.d 出现NEP时,开发者无法确定究竟是a、b、c、d中的那个变量报了空指针。而在这个新特点的加入之后,NEP错误栈则会明确表明,触发NEP的对象是哪个。
示例UML类图
错误栈体现
public class Feature358 {
/**
* @Description 查看旧版本的NEP错误栈
*/
@Test
public void testOld() {
MiddleClass middleClass = new MiddleClass();
OuterClass outerClass = new OuterClass();
outerClass.middleClass = middleClass;
// 错误栈(无法定位到具体是那个对象报了空指针异常):
// java.lang.NullPointerException
// at com.keiissland.feature.feature358.Feature358.testOld(Feature358.java:19)
System.out.println(outerClass.middleClass.innerClass.property);
}
/**
* @Description 查看新版本的NEP错误栈
* 需添加启动参数 -XX:+ShowCodeDetailsInExceptionMessages
*/
@Test
public void testNew() {
MiddleClass middleClass = new MiddleClass();
OuterClass outerClass = new OuterClass();
outerClass.middleClass = middleClass;
// 错误栈(定位到发生异常的对象是innerClass):
// java.lang.NullPointerException: Cannot read field "property" because "outerClass.middleClass.innerClass" is null
System.out.println(outerClass.middleClass.innerClass.property);
// 错误栈(定位到发生异常的对象是middleClass):
// java.lang.NullPointerException: Cannot read field "innerClass" because "outerClass.middleClass" is null
outerClass.middleClass = null;
System.out.println(outerClass.middleClass.innerClass.property);
}
}
records 是Java语言中一种新的类型声明。和Enum一样,record 也是类的一种限制形式。record 声明其自身表示方法,并提供了与该表示匹配的API。record 放弃了类通常享有的自由(无法进行属性的set):将API与表示分离的能力。作为回报,record 获得了很大程度的简洁性。
定义record
/**
* JEP 305: Records (Preview)
* Records(预览)
* 定义了一个包含name和desc属性的record
*/
public record Feature359(String name, String desc) {
public static void main(String[] args) {
// record 类头声明是它的一个构造方法
Feature359 record = new Feature359("Record", "Hello Record!");
}
}
类的部分(或全部)状态,可以直接放在类头中进行描述。
反编译
public final class Feature359 extends java.lang.Record {
private final java.lang.String name;
private final java.lang.String desc;
/**
* 由类头描述直接得到
*/
public Feature359(java.lang.String name, java.lang.String desc) { /* compiled code */ }
public static void main(java.lang.String[] args) { /* compiled code */ }
public java.lang.String toString() { /* compiled code */ }
public final int hashCode() { /* compiled code */ }
public final boolean equals(java.lang.Object o) { /* compiled code */ }
public java.lang.String name() { /* compiled code */ }
public java.lang.String desc() { /* compiled code */ }
}
通过反编译可以看出record类型的提供了类似Lombok中的@Data注解的功能。自动添加了toString( )、hashCode( )、*equals( )*等方法。正如官方文档给出的说明一样,record 是类的一种限制形式(不可变),因此我们可以看到record 是final类型的,并且其属性也是final类型的。作为回报,record 获得了很大程度的简洁性。
record 语法支持
record 支持添加静态属性、静态方法、构造器、实例方法。开发者可以根据自己的需要添加对应的属性或方法。
record 语法限制
record 不允许声明非静态的属性,不可以使用abstract进行修饰。由于用record声明的类,已经继承了java.lang.Record 故,record 不能在显示的集成其它类。
补充说明
为了对record 的支持,java.lang.Class
引入了*isRecord( )和getRecordComponents( )方法。其中isRecord( )*为判断一个类是否用record进行声明的,*getRecordComponents( )*获取record 中的属性数组RecordComponent[ ]。该数组中包含属性的类型,和属性的值。
switch 表达式。扩展switch使其可以用作语句或表达式,以便语句或表达式都可以使用传统语法(case …:…)或新语法(case … -> …) ,还有另一个语法,用于从switch表达式产生值。 这些更改将简化日常编码,并为switch中使用模式匹配提供了方法。 这是JDK 12和JDK 13中的预览语言功能。
代码体现
WeekEnum.java
enum WeekEnum {
/**
* 周一、周二……以此类推
*/
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}
老版本使用姿势
@Test
public void testOld() {
WeekEnum weekDay = SUNDAY;
switch (weekDay) {
case MONDAY :
case TUESDAY:
case WEDNESDAY :
case THURSDAY:
case FRIDAY:
System.out.println("工作日");
break;
case SATURDAY:
case SUNDAY:
System.out.println("节假日");
break;
default:
System.out.println("参数错误");
}
}
新版本使用姿势一
/**
* 支持case的合并
*/
@Test
public void testNew() {
WeekEnum weekDay = SUNDAY;
switch (weekDay) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> System.out.println("工作日");
case SATURDAY, SUNDAY -> System.out.println("节假日");
default -> System.out.println("参数错误");
}
}
新版本使用姿势二
/**
* switch 作为一个有返回值的表达式
*/
@Test
public void testNewExpress() {
WeekEnum weekDay = SUNDAY;
// 可获取switch表达式返回值
String weekDesc = switch (weekDay) {
// -> 后的字符串,直接作为表达式的返回结果
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "工作日";
case SATURDAY, SUNDAY -> "节假日";
default -> "参数错误";
};
System.out.println(weekDesc);
}
新版本使用姿势三
/**
* 通过yield指定switch表达式的返回结果
*/
@Test
public void testNewWithYield() {
WeekEnum weekDay = SUNDAY;
String weekDesc = switch (weekDay) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> {
System.out.println("今天是工作日");
// 通过yield指定表达式的返回结果
yield "工作日";
}
case SATURDAY, SUNDAY -> {
System.out.println("节假日");
// 通过yield指定表达式的返回结果
yield "工作日";
}
default -> "参数错误";
};
System.out.println(weekDesc);
}
文本块。将文本块添加到Java语言。 文本块是多行字符串文字,它避免了大多数转义序列的需要,以一种可预测的方式自动设置字符串的格式,并在需要时使开发人员可以控制格式。 这是JDK 14中的预览语言功能。老版本中的JDK中,开发中通常使用各种转移字符、“+”等方法来达到文本排版的效果。该特性通过引入
'''
,来对文本块直接进行包裹,增加了代码的可读性。
老版本字符串拼接
充斥着大量的转移字符和拼接符,不利于程序的阅读
@Test
public void testOld() {
String html = "\n" +
" \n" +
" Hello, world
\n" +
" \n" +
"\n";
String query = "SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`\n" +
"WHERE `CITY` = 'INDIANAPOLIS'\n" +
"ORDER BY `EMP_ID`, `LAST_NAME`;\n";
String json = "{\n" +
" \"student\": {\n" +
" \"name\": \"小明\",\n" +
" \"age\": \"15\",\n" +
" \"school\": {\n" +
" \"local\": \"武汉\"\n" +
" }\n" +
" }\n" +
"}\n";
}
新版本字符串拼接
通过'''
对html进行包裹,程序的易读性大大提升,同时'''
还支持SQL、JSON的处理
@Test
public void testNew() {
String html = """
<html>
<body>
<p>Hello, world</p>
</body>
</html>
""";
String query = """
SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`
WHERE `CITY` = 'INDIANAPOLIS'
ORDER BY `EMP_ID`, `LAST_NAME`;
""";
String json = """
{
"student": {
"name": "小明",
"age": "15",
"school": {
"local": "武汉"
}
}
}
""";
}
弃用 ParallelScavenge + SerialOld GC 组合,官方给出的解释如下:
我们认为GC算法的组合很少使用,维护这种组合所需要的工作量是巨大的。这种组合本身就是是不寻常的,因为它在年轻代中采用的是并行GC算法,而在老年代中采用的是串行的GC算法。我们认为这种组合仅对年轻代空间很大和老年代空间很小的系统部署有用。在这种情况下,由于老年代的体积很小,因此完整的垃圾收集暂停时间可能是可以接受的。实际上,这是一种非常罕见且危险的部署,因为年轻一代中对象的活动性稍有变化将导致OutOfMemoryException,因为老年代的内存显着小于年轻代。与针对年轻代和老年代使用并行GC算法相比,此组合的唯一优势是总内存使用量略低。我们认为,这种较小的内存占用优势(最多约为Java堆大小的3%)不足以超过维护此GC组合的成本。
移除 CMS(Concurrent Mark Sweep)垃圾收集器,主要是其存在如下缺点:
当CMS停止工作时,会把Serial Old GC 作为备选方案,而Serial Old GC 是JVM中性能最差的垃圾回收算法。
参考文档
http://openjdk.java.net/projects/jdk/14/