1. 简介
Java 15于2020年9月全面上市,是JDK平台14的下一个短期版本。它建立在早期版本的多项功能之上,并提供了一些新的增强功能。
在这篇文章中,我们将介绍Java 15的一些新特性,以及Java开发人员感兴趣的其他变化。
2. Record(JEP 384)
记录是Java中的一种新型类,可以让创建不可变数据对象变得更容易。
它最初作为早期预览在Java 14中引入,Java 15旨在它成为正式产品功能之前改进一些方面。
让我们看一个使用当前Java的示例,以及它如何随记录而变化。
2.1 不使用Record
在记录出现之前,我们创建一个不可变数据传输对象(DTO)需要像下面这样:
public class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
请注意,这里用了很多代码来创建一个真正只保存状态的不可变对象。我们所有的字段都是使用final显式定义的,我们有一个单一的全参数构造函数,并且每个字段都有一个访问器方法。在某些情况下,我们甚至可以将类本身声明为final以防止任何子类化。
在许多情况下,我们还需要进一步重写toString方法以提供有意义的日志记录输出。我们还可能重写equals和hashCode方法,以避免在比较这些对象的两个实例时出现意外后果。
2.2 使用Record
使用新的记录类,我们可以以更紧凑的方式定义相同的不可变数据对象:
public record Person(String name, int age) {
}
这里发生了一些事情。首先也是最重要的一点,类定义具有特定于记录的新语法。我们在Person后的括号内提供有关记录中字段的详细信息。
使用此标头,编译器可以推断出内部字段。这意味着我们不需要定义特定的成员变量和访问器,因为它们是默认提供的,并且我们也不必提供构造函数。
此外,编译器还为toString、equals和hashCode方法提供了合理的实现。
虽然记录提供了许多样板代码,但它也允许我们覆盖一些默认行为。例如,我们可以定义一个规范的构造函数来做一些验证:
public record Person(String name, int age) {
public Person {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
}
}
值得一提的是,记录也有一些限制。除其他事项外,它们始终是final的,不能声明为抽象的,并且它们不能使用本地方法。
3. 密封类(JEP 360)
目前,Java不提供对继承的细粒度控制。public、protected、private等访问修饰符以及默认的package-private提供非常粗粒度的控制。
为此,密封类的目标是允许各个类声明哪些类型可以用作子类型。这也适用于接口并确定哪些类型可以实现它们。
密封类涉及两个新的关键字-sealed和permits:
public abstract sealed class Person
permits Employee, Manager {
//...
}
在本例中,我们声明了一个名为Person的抽象类,并且指定了唯一可以扩展它的类是Employee和Manager。扩展密封类与我们一直使用的方式相同,即使用extends关键字:
public final class Employee extends Person {
}
public non-sealed class Manager extends Person {
}
需要注意的是,任何扩展密封类的类本身都必须声明为sealed、non-sealed或final。这可确保类层次结构保持有限并为编译器所知。
这种有限而详尽的层次结构是使用密封类的一大好处,让我们看一个实际的例子:
if (person instanceof Employee) {
return ((Employee) person).getEmployeeId();
}
else if (person instanceof Manager) {
return ((Manager) person).getSupervisorId();
}
如果没有密封类,编译器就无法合理地确定所有可能的子类都包含在我们的if-else语句中。如果在末尾没有else子句,编译器可能会发出警告,表明我们的逻辑并未涵盖所有情况。
4. 隐藏类(JEP 371)
Java 15中引入的一个新特性称为隐藏类。虽然大多数开发人员不会直接从它身上获得好处,但使用动态字节码或JVM语言的任何人都可能会发现它们很有用。
隐藏类的目标是允许在运行时创建不可发现的类,这意味着它们不能被其他类链接,也不能通过反射被发现。诸如此类的类通常具有较短的生命周期,因此,隐藏类被设计为高效加载和卸载。
请注意,当前的Java版本允许创建类似于隐藏类的匿名类。但是,它们依赖于Unsafe API,而隐藏类没有这种依赖性。
5. 模式匹配类型检查(JEP 375)
模式匹配功能在Java 14中进行了预览,而Java 15旨在继续其预览状态而没有新的增强功能。
作为回顾,此功能的目标是删除大量通常与instanceof运算符一起提供的样板代码:
if (person instanceof Employee) {
Employee employee = (Employee) person;
Date hireDate = employee.getHireDate();
// ...
}
这是Java中非常常见的模式,每当我们检查一个变量是否是某种类型时,我们几乎总是在它之后进行对该类型的强制转换。
模式匹配功能通过引入新的绑定变量来简化此操作:
if (person instanceof Employee employee) {
Date hireDate = employee.getHireDate();
// ...
}
请注意我们是如何提供一个新的变量名称employee作为类型检查的一部分的。如果类型检查为true,那么JVM会自动为我们强制转换变量,并将结果分配给新的绑定变量。
我们还可以将新的绑定变量与条件语句结合起来:
if (person instanceof Employee employee && employee.getYearsOfService() > 5) {
// ...
}
在未来的Java版本中,目标是将模式匹配扩展到其他语言功能,例如switch语句或者是record。
6. 外部内存API(JEP 383)
外部内存访问已经是Java 14的一个孵化特性。在Java 15中,目标是继续其孵化状态,同时添加几个新特性:
- 一个新的VarHandle API,用于自定义内存访问变量句柄
- 支持使用Spliterator接口并行处理内存段
- 增强了对映射内存段的支持
- 能够操纵和取消引用来自本地调用之类的地址
外部内存通常是指位于托管JVM堆之外的内存。因此,它不受垃圾收集的影响,通常可以处理非常大的内存段。
虽然这些新的API可能不会直接影响大多数开发人员,但它们将为处理外部内存的第三方库提供很多价值。这包括分布式缓存、非规范化文档存储、大型任意字节缓冲区、内存映射文件等。
7. 垃圾收集器
在Java 15中,ZGC(JEP 377)和Shenandoah(JEP 379)都不再是实验性功能了,它们都将是团队可以选择使用的受支持配置,而G1收集器将保持默认设置。
两者以前都可以使用实验性功能标志获得,这种方法允许开发人员测试新的垃圾收集器并提交反馈,而无需下载单独的JDK或附加组件。
关于Shenandoah的一个注意事项:并非所有供应商的JDK都提供它,最值得注意的是,Oracle JDK不包含它。
8. 其他变化
Java 15中还有其他几个值得注意的变化。
经过Java 13和14的多轮预览后,文本块将成为Java 15中全面支持的产品特性。
友好的空指针异常,最初在JEP 358下的Java 14中提供,现在默认启用。
旧版的DatagramSocket API已被重写,这是Socket API在Java 14中重写的后续。虽然它不会影响大多数开发人员,但它很有趣,因为它是Project Loom的先决条件。
另外值得注意的是,Java 15包括对Edwards-Curve数字签名算法的加密支持。EdDSA是一种现代椭圆曲线签名方案,与JDK中现有的签名方案相比具有多项优势。
最后,Java 15中弃用了几项内容:偏向锁定、Solaris/SPARC端口和RMI Activation都已删除或计划在未来版本中删除。
值得注意的是,最初在Java 8中引入的Nashorn JavaScript引擎现已被删除。随着最近GraalVM和其他VM技术的引入,很明显Nashorn在JDK生态系统中不再占有一席之地。
9. 总结
Java 15建立在过去版本的多项功能之上,包括记录、文本块、新的垃圾收集算法等。它还添加了新的预览功能,包括密封类和隐藏类。
由于Java 15不是一个长期支持版本,我们预计对它的支持将于2021年3月结束。届时,我们可以期待Java 16,紧随其后的是新的长期支持版本Java 17。
与往常一样,本教程的完整源代码可在GitHub上获得。