JDK14包含了比JDK12和JDK13加在一起还多的特性。共包括16项的新特性。
一、instanceof的模式匹配
通过模式匹配,开发者可以用更简洁和更安全的方式来表达通用的程序逻辑。instanceof 运算符的模式匹配支持从对象中按条件来提取组件,此语言特性目前处于预览阶段。
模式由两个部分组成:
1、对目标对象进行检查的断言(predicate)。
2、当断言成立时,从目标对象中提取值的绑定变量。
在 Java 14 之前我们需要这样写代码:
Object object = someOneObj();
if(object instanceof String){
boolean isContainsA = ((String) object).contains("A");
}
java14这样写就行:
Object object = someOneObj();
if (object instanceof String str) { // 注意这里更换了变量名
boolean isContainsA = str.contains("A");
}
// 还能继续加入判断条件
if (object instanceof String str && str.contains("A")) {
System.out.println(str);
}
Switch表达式在经过JDK 12(JEP 325)和JDK(JEP 354)的预览之后,在JDK 14中已经稳定可用。Switch表达式包括如下几个内容。
可以使用箭头标签进行匹配。当箭头标签匹配时,只有对应的语句或表达式会执行,没有fall-through。不允许fall-through可以避免很多潜在的问题。
在
switch
表达式中不能使用
break
。
switch
表达式的每个标签都必须产生一个值,或者抛出异常。
switch
表达式必须穷尽所有可能的值。这意味着通常需要一个
default
语句。一个例外是枚举类型。如果穷尽了枚举类型的所有可能值,则不需要使用
default
。在这种情况下,编译器会自动生成一个
default
语句。这是因为枚举类型中的枚举值可能发生变化。
java14之前这样写:
String day = getDay();
switch (day) {
case "周一":
case "周二":
System.out.println("这里是周一和周二");
break;
case "周三":
System.out.println("这里是周三");
break;
case "周四":
System.out.println("这里是周四");
break;
default:
System.out.println("这里是周五六日");
break;
}
java14这样写:
String day = getDay();
switch (day) {
case "周一", "周二" -> System.out.println("这里是周一和周二");
case "周三" -> System.out.println("这里是周三");
case "周四" -> System.out.println("这里是周四");
default -> System.out.println("这里是周五六日");
}
新增返回值:
boolean isWorkday = switch (day) {
case "周六", "周日" -> false;
default -> true;
};
当使用箭头标签时,箭头标签右边可以是表达式、
throw
语句或是代码块。如果是代码块,需要使用
yield
语句来返回值
boolean isWorkday = switch (day) {
case "周六", "周日" -> false;
default -> {
boolean isWeekend = isWeekend(day);
yield isWeekend;
}
};
三、Record语法
Records 提供了一种紧凑的语法来声明类,以帮助开发者写出更简洁的代码,这些类是浅层不可变数据(shallowly immutable data)的透明拥有者。该特性主要用在特定领域的类,这些类主要用于保存数据,不提供领域行为。
记录类型的作用类似于Kotlin中的数据类(data class)和Scala中的case class。记录类型的作用是作为不可变数据的封装。类似于枚举类型,记录类型是一种形式受限的类。一个记录类型由名称和状态描述两部分组成。状态描述声明了记录中包含的组件。
下面代码中的 Pair
就是一个记录类型。记录类型使用 record
声明。 Pair
记录类型的状态描述由 first
和 second
两个 Object
类型的组件组成。可以通过 first()
和 second()
方法来访问。
public class Records { record Pair(Object first, Object second) {} public static void main(String[] args) { Pair pair = new Pair("Hello", "World"); System.out.println(pair.first()); System.out.println(pair.second()); } }
记录类型有自动生成的成员,包括:
状态描述中的每个组件都有对应的 private final
字段。
状态描述中的每个组件都有对应的 public
访问方法。方法的名称与组件名称相同。
一个包含全部组件的公开构造器,用来初始化对应组件。
实现了 equals()
和 hashCode()
方法。 equals()
要求全部组件都必须相等。
实现了 toString()
,输出全部组件的信息。
我们可以通过 javap
命令查看 Pair
类型的字节代码,如下所示。从中可以看到, Pair
继承自 java.lang.Record
。这一点和所有的枚举类型都继承自 java.lang.Enum
是相似的。
Compiled from "Pair.java" public final class io.vividcode.javafeatures.Pair extends java.lang.Record { public io.vividcode.javafeatures.Pair(java.lang.Object, java.lang.Object); public java.lang.String toString(); public final int hashCode(); public final boolean equals(java.lang.Object); public java.lang.Object first(); public java.lang.Object second(); }
相对于普通的类,记录类型有一些限制。记录类型不能继承自其它类,不能有其他实例字段。所有的其他字段都必须是 static
的。记录类型都是 final
的,也不能是 abstract
的。记录中的组件也都是 final
的。
如果需要在记录类型的构造器中添加校验或规格化的逻辑,可以定义自己的构造器。下面代码中的 Point
记录类型对 x
和 y
的参数值进行了处理。如果构造器不带形式参数(类似下面代码中的方式),则认为是要覆写默认生成的带全部组件的构造器(即覆写 Point(int x, int y)
)。当自定义构造器退出时,所有未显式赋值的字段,都会自动用参数来赋值。如果下面代码中的构造器没有对 y
赋值,那么 y
的值会通过 this.y = y
的方式被赋值。这样可以简化实现,同时确保记录类型的对象处在正确的状态中。
record Point(int x, int y) { public Point { this.x = Math.max(x, 0); this.y = Math.max(y, 0); } }
三、字符串块
文本块是使用3个引号分隔的多行字符串。根据文本块在源代码中的出现形式,多余的用于缩进的白字符会被去掉。相应的算法会在每一行中去掉同样数量的白字符,直到其中任意的一行遇到非白字符为止。每一行字符串末尾的白字符会被自动去掉。
java14之前这样写
String str = "<html>" +
"<body>" +
"<div>body</div>" +
"</body>" +
"</html>";
java14这样写
// 注意 """ 之后必须换行
String str = """
<html>
<header>
</header>
<body>
<div>"body"</div>
</body>
</html>
""";
四、 改进NullPointerExceptions
改进 NullPointerExceptions,通过准确描述哪些变量为 null 来提高 JVM 生成的异常的可用性。该提案的作者希望为开发人员和支持人员提供有关程序为何异常终止的有用信息,并通过更清楚地将动态异常与静态程序代码相关联来提高对程序的理解。
user.getChild().getName().someMethod();
增强了对 NullPointerException
异常的处理,可以显示详细的信息。这个功能需要通过选项 -XX:+ShowCodeDetailsInExceptionMessages
启用,如下所示: Exception
in
thread
"main"
java.lang.NullPointerException:
Cannot read field 'user' because 'user.getChild()' is null.
at Prog.main(Prog.java:5)
五、 非易失性映射的字节缓冲
该JEP使得可以通过
FileChannel.map()
方法创建指向非易失性内存(non-volatile memory,NVM)的
MappedByteBuffer
对象。
六、 打包工具
Java 14 计划引入打包功能,以简化依赖于各种依赖项的安装过程。有时仅仅提供一个JAR文件是不够的;它必须提供一个适合本地/本机的可安装工具包。打包工具还可以帮助填补其他技术留下的空白。
jpackage工具将Java应用程序捆绑到一个特定于平台的包中,该包包含所需的所有依赖项。作为一组普通的JAR文件或模块集合。支持的特定于平台的包格式包括:
Linux:deb和rpm
macOS:pkg和dmg
Windows:msi和exe
七、 JFR 事件流
Java 14 正计划提供一个API,通过该API,JDK飞行记录器(JFR)收集的数据将连续监视进程内和进程外应用程序。
可以以非流式方式记录相同的事件集,如果可能的话,开销小于1%。因此,事件流将与非流同时执行。
jdk.jfr模块中的jdk.jfr.consumer包扩展了异步订阅事件的功能 。
下面的代码展示了事件流的用法。通过 RecordingStream
来创建事件流,使用 onEvent()
方法注册对特定事件的处理器,最后启动流即可。 FlightRecorder.getFlightRecorder().getEventTypes()
方法可以得到全部的事件。下面的代码对所有的事件都注册了处理器,直接把事件输出到控制台。
import jdk.jfr.FlightRecorder; import jdk.jfr.consumer.RecordingStream; public class JFRStream { public static void main(String[] args) { try (var rs = new RecordingStream()) { FlightRecorder.getFlightRecorder().getEventTypes() .forEach(eventType -> rs.onEvent(eventType.getName(), System.out::println)); rs.start(); } } }
下面的命令用来运行上面的代码,其中选项 -XX:StartFlightRecording
用来启动JFR。
$ java -XX:StartFlightRecording io/vividcode/javafeatures/JFRStream.java
喜欢 就关注吧,欢迎投稿!