Java 14 GA(General Availability) 版本已经于3月17日发布。
Java 14 包含的 JEP(Java Enhancement Proposals 的缩写,Java 增强建议)比 Java 12 和 13 两个版本加起来还要多。那么,对于每天编写和维护代码的 Java 开发人员来说,哪个特性值得我们关注呢?
本文我将介绍以下几个重要功能:
• 改进的 switch 表达式,最初作为预览在 Java 12 和 Java 13 中出现,现在完全成为 Java 14 的一部分。 • instanceof 模式匹配(Pattern matchin),这是一个语言特性; • 非常有用的 NullPointerExceptions,是 JVM 的一个特性。
如果你阅读了本文并在你的代码库中尝试了其中的一些特性,你可以通过向 Java 团队提供反馈来分享你的经验。通过这样做,您就有机会为 Java 的开发做出贡献。好了,咱们废话不多说,现在来详细看看 Java 14 值得每一个 Java 关注的新特性。
在 Java 14 中,switch 表达式正式成为语言的一部分。如果你不知道 switch 表达式是什么,可以看下这几篇文章 New switch Expressions in Java 12 [1] 、 《Inside Java 13’s switch Expressions and Reimplemented Socket API》 [2] 、以及 《 JDK 13 都已经来了!五大新特性你最喜欢哪个 ? 》。
在以前 Java 12 和 Java 13 的版本中,switch 表达式是一个“预览”特性。这里注意,被指定为“预览”的特性主要用以收集反馈,并可能根据反馈进行更改甚至删除;不过其中的大多数最终将成为 Java 一部分。
新的 switch 表达式的好处包括减少了 bug 的范围,而且得助于表达式和复合形式,使 switch 的编写变得简单。比如 switch 表达式现在可以利用箭头语法:
var log = switch (event) {
case PLAY -> "User has triggered the play button";
case STOP, PAUSE -> "User needs a break";
default -> {
String message = event.toString();
LocalDateTime now = LocalDateTime.now();
yield "Unknown event " + message +
" logged on " + now;
}
};
Java 13 首次引入了文本块,并且作为预览特性。文本块使处理多行字符串更容易。在 Java 14 中,这个特性仍然是预览特性,并做了一些调整。在之前,为了提供足够的多行文本格式,使用许多字符串连接和转义序列来编写代码是很常见的。比如下面的代码展示了一个 HTML 格式化的例子:
String html = "<HTML>" +
"/n/t" + "<BODY>" +
"/n/t/t" + "<H1>/"Java 14 is here!/"</H1>" +
"/n/t" + "</BODY>" +
"/n" + "</HTML>";
有了文本块之后,你可以简化这个过程,并使用分隔文本块开头和结尾的三个引号编写更优雅的代码:
String html = """
<HTML>
<BODY>
<H1>"Java 14 is here!"</H1>
</BODY>
</HTML>""";
与普通字符串文字相比,文本块还提供了更强的表达能力。你可以阅读这篇文章:《Text Blocks Come to Java》。
在 Java 14 中添加了两个新的转义字符。首先,可以使用新的 /s
转义字符表示单个空格。其次,可以使用反斜杠 /
来禁止在行的末尾插入新行字符。当你想要在文本块中分割一个很长的行以提高可读性时,这是很有帮助的。
比如,当前处理多行字符串的方法如下:
String literal =
"Lorem ipsum dolor sit amet, consectetur adipiscing " +
"elit, sed do eiusmod tempor incididunt ut labore " +
"et dolore magna aliqua.";
有了 / 转义字符之后,我们可以在文本块中这么来写:
String text = """
Lorem ipsum dolor sit amet, consectetur adipiscing /
elit, sed do eiusmod tempor incididunt ut labore /
et dolore magna aliqua./
""";
Java 14 引入了一个预览特性,该特性有助于消除在执行 instanceof 检查之后,还需要显式强制转换的需要。例如,考虑以下代码:
if (obj instanceof Group) {
Group group = (Group) obj;
// use group specific methods
var entries = group.getEntries();
}
可以使用这个新功能,将上面代码重构如下:
if (obj instanceof Group group) {
var entries = group.getEntries();
}
因为条件检查已经判断出 obj 是 Group 类型的,那么为什么还需要在代码中显示转换 obj 为 Group 类型呢?而且这可能会增加错误的范围。
新的语法将从典型的 Java 程序中删除许多类型转换。(2011年的一份研究报告显示,大约24%的 cast 都是在进行 instanceof 判断之后进行的。)
JEP 305 涵盖了这一变化,并指出了来自 Joshua Bloch 的《Effective Java book》中的一个示例,该示例如下:
@Override public boolean equals(Object o) {
return (o instanceof CaseInsensitiveString) &&
((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
}
通过删除对 CaseInsensitiveString 的显式强制转换,可以将前面的代码简化为以下形式:
@Override public boolean equals(Object o) {
return (o instanceof CaseInsensitiveString cis) &&
cis.s.equalsIgnoreCase(s);
}
这是一个值得体验的有趣特性,因为它为更通用的模式匹配打开了大门。模式匹配的思想是为基于一定条件提取对象的组件提供一种语法方便的语言特征。instanceof 操作符就是这种情况,因为条件是类型检查,提取过程调用适当的方法或访问特定的字段。
换句话说,这个预览特性只是一个开始,我们可以期待一个能够帮助进一步减少冗余代码的语言特性,从而减少 bug 的可能性。
另一个值得关注的预览语言特性:records。与目前提出的其他特性一样,该特性遵循了减少 Java 语言冗长的趋势,并帮助开发人员编写更简洁的代码。Recods 关注特定的域类(domain classes ),这些域类的目的只是在字段中存储数据,并且不声明任何自定义行为。
为了说明这个特性,假设我们有一个域类 BankTransaction,它用三个字段构建一个事务:日期、金额和描述。由于我们需要考虑和其他组件进行交互,所以我们还需要以下一些方法:
• 构造器 • Getter 方法 • toString() 方法 • hashCode() 和 equals() 方法 这些代码通常由 IDE 自动生成,占用大量空间。下面是完整生成的 BankTransaction 类实现:
public class BankTransaction {
private final LocalDate date;
private final double amount;
private final String description;
public BankTransaction(final LocalDate date,
final double amount,
final String description) {
this.date = date;
this.amount = amount;
this.description = description;
}
public LocalDate date() {
return date;
}
public double amount() {
return amount;
}
public String description() {
return description;
}
@Override
public String toString() {
return "BankTransaction{" +
"date=" + date +
", amount=" + amount +
", description='" + description + '/'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BankTransaction that = (BankTransaction) o;
return Double.compare(that.amount, amount) == 0 &&
date.equals(that.date) &&
description.equals(that.description);
}
@Override
public int hashCode() {
return Objects.hash(date, amount, description);
}
}
Java 14 提供了一种方法来删除这些代码,并明确表示您想要的只是一个类,它只将数据与 equals、hashCode 和 toString 方法的实现聚合在一起。您可以重构 BankTransaction 如下:
public record BankTransaction(LocalDate date,
double amount,
String description) {}
使用 record 来标记我们的类,我们的类将自动获取 equals、hashCode 和 toString 方法,除此之外,类的构造函数和 getters 方法也将自动得到。
如果你想要尝试这个功能,在编译这个类的时候你需要加上预览标记:
javac --enable-preview --release 14 BankTransaction.java
record 标记的类中 Fields 将隐式设置成 final,也就意味着我们不能修改它。但是需要注意的是,这并不意味着 recod 标记的类是不可修改的(immutable)。存储在字段中的对象本身可以是可变的。
有些人说,抛出 NullPointerExceptions 应该成为 Java 中新的“Hello world”,因为我们无法摆脱它们。撇开玩笑不谈,当代码在生产环境中运行时,NullPointerExceptions 经常出现在应用程序日志中,这可能会使调试变得困难,因为原始代码并不容易获得。例如,考虑下面的代码:
var name = user.getLocation().getCity().getName();
在 Java 14 之前,你可能得到以下的错误:
Exception in thread "main" java.lang.NullPointerException
at NullPointerExample.main(NullPointerExample.java:5)
不幸的是,在第5行代码中,有多个方法调用的赋值——getLocation() 和 getCity()——其中任何一个都可能返回 null。实际上,变量 user 也可以是 null。所以,目前还不清楚是什么导致了 NullPointerException。
不过在 Java 14,新的 JVM 特性将给我们提供更多有用的信息:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Location.getCity()" because the return value of "User.getLocation()" is null
at NullPointerExample.main(NullPointerExample.java:5)
上面的异常信息包含两个方面的信息:
• 结果:Location.getCity() 无法被调用; • 原因:User.getLocation() 的返回值是 null。要使用这个功能,需要在 JVM 中加入以下标记
-XX:+ShowCodeDetailsInExceptionMessages
以下是一个示例:
java -XX:+ShowCodeDetailsInExceptionMessages NullPointerExample
根据 JDK-8233014,在未来的 Java 版本,这个特性可能会默认启用。
这种增强不仅可用于方法调用,还可用于其他可能导致 NullPointerException 的地方,包括字段访问、数组访问和赋值等。
好了今天的分享就到这里了。
本文翻译自: Java 14 Arrives with a Host of New Features [3]
[1]
New switch Expressions in Java 12: https://blogs.oracle.com/javamagazine/new-switch-expressions-in-java-12
[2]
《Inside Java 13’s switch Expressions and Reimplemented Socket API》: https://blogs.oracle.com/javamagazine/inside-java-13s-switch-expressions-and-reimplemented-socket-api
[3]
Java 14 Arrives with a Host of New Features: https://blogs.oracle.com/javamagazine/java-14-arrives-with-a-host-of-new-features