Contents

Java 15中的新功能

1. 简介

Java 15 于 2020 年 9 月全面上市,是 JDK 平台的下一个短期版本。它建立在早期版本的多项功能之上,还提供了一些新的增强功能。 在本文中,我们将了解 Java 15 的一些新特性,以及 Java 开发人员感兴趣的其他变化。

2. 记录(JEP 384)

记录 是 Java 中的一种新型类,可以轻松创建不可变数据对象。 Java 15最初作为早期预览版在 Java 14 中引入,旨在在成为正式产品功能之前完善一些方面 。 让我们看一个使用当前 Java 的示例,以及它如何随记录而变化。

2.1. 没有记录

在记录之前,我们将创建一个不可变数据传输对象 (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方法以提供有意义的日志输出。我们可能还想覆盖equalshashCode方法,以避免在比较这些对象的两个实例时出现意外后果。

2.2. 有记录

使用新的record类,我们可以以更紧凑的方式定义相同的不可变数据对象:

public record Person(String name, int age) {
}

这里发生了一些事情。首先,类定义具有特定于record的新语法。这个标题是我们提供有关记录中字段的详细信息的地方。

使用此标头,编译器可以推断出内部字段。这意味着我们不需要定义特定的成员变量和访问器,因为它们是默认提供的。我们也不必提供构造函数。 此外,编译器为toStringequalshashCode方法提供了合理的实现。 虽然record消除了很多样板代码,但它们确实允许我们覆盖一些默认行为。例如,我们可以定义一个规范的构造函数来做一些验证:

public record Person(String name, int age) {
    public Person {
        if(age < 0) {
            throw new IllegalArgumentException("Age cannot be negative");
        }
    }
}

值得一提的是record确实有一些限制。除其他事项外,它们始终是最终的,不能声明为抽象的,并且不能使用本地方法

3. 密封类(JEP 360)

目前,Java 不提供对继承的细粒度控制publicprotectedprivate等访问修饰符以及默认的 package-private 提供非常粗粒度的控制。

为此,sealed 的目标是允许各个类声明哪些类型可以用作子类型。这也适用于接口并确定哪些类型可以实现它们。 密封类涉及两个新关键字 — sealedpermits

public abstract sealed class Person
    permits Employee, Manager {
 
    //...
}

在这个例子中,我们声明了一个名为 Person 的抽象类*。我们还指定了唯一可以扩展它的类是EmployeeManager*。扩展密封类就像今天在 Java 中一样,使用extends关键字:

public final class Employee extends Person {
}
public non-sealed class Manager extends Person {
}

请务必注意,任何扩展密封类的类本身都必须声明为sealednon-sealedfinal。这确保类层次结构保持有限并为编译器所知。 这种有限而详尽的层次结构是使用密封类的一大好处。让我们看一个实际的例子:

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作为类型检查的一部分。如果类型检查为,那么JVM 会自动为我们转换变量并将结果分配给新的绑定变量。 我们还可以将新的绑定变量与条件语句结合起来:

if (person instanceof Employee employee && employee.getYearsOfService() > 5) {
    //...
}

在未来的 Java 版本中,目标是将模式匹配扩展到其他语言功能,例如switch语句。

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 包括对爱德华兹曲线数字签名算法的加密支持。EdDSA 是一种现代椭圆曲线签名方案,与 JDK 中现有的签名方案相比具有多项优势。 最后,Java 15 中弃用了几项内容。偏向锁定、Solaris/SPARC 端口和 RMI 激活都已删除或计划在未来版本中删除。 值得注意的是,最初在 Java 8 中引入的 Nashorn JavaScript 引擎现已被删除。随着最近引入 GraalVM 和其他 VM 技术,很明显 Nashorn 在 JDK 生态系统中不再占有一席之地。