New Features in Java 16 – Java 16的新功能

最后修改: 2021年 9月 22日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Java 16, released on the 16th of March 2021, is the latest short-term incremental release building on Java 15. This release comes with some interesting features, such as records and sealed classes.

Java 16于2021年3月16日发布,是建立在Java 15之上的最新短期增量版本。这个版本带有一些有趣的功能,例如记录和密封类。

In this article, we’ll explore some of these new features.

在这篇文章中,我们将探讨这些新功能中的一些。

2. Invoke Default Methods From Proxy Instances (JDK-8159746)

2.从代理实例中调用默认方法(JDK-8159746)

As an enhancement to the default method in Interfaces, with the release of Java 16, support has been added to java.lang.reflect.InvocationHandler invoke default methods of an interface via a dynamic proxy using reflection.

作为对接口中默认方法的增强,随着 Java 16 的发布,已经为 java.lang.reflect.InvocationHandler 通过动态代理使用反射调用接口的默认方法添加了支持。

To illustrate this, let’s look at a simple default method example:

为了说明这一点,让我们看看一个简单的默认方法的例子。

interface HelloWorld {
    default String hello() {
        return "world";
    }
}

With this enhancement, we can invoke the default method on a proxy of that interface using reflection:

有了这个增强,我们可以使用反射在该接口的代理上调用默认方法。

Object proxy = Proxy.newProxyInstance(getSystemClassLoader(), new Class<?>[] { HelloWorld.class },
    (prox, method, args) -> {
        if (method.isDefault()) {
            return InvocationHandler.invokeDefault(prox, method, args);
        }
        // ...
    }
);
Method method = proxy.getClass().getMethod("hello");
assertThat(method.invoke(proxy)).isEqualTo("world");

3. Day Period Support (JDK-8247781)

3.日期间支持(JDK-8247781)

A new addition to the DateTimeFormatter is the period-of-day symbol “B“, which provides an alternative to the am/pm format:

DateTimeFormatter的新成员是日周期符号”B“,它提供了一个上午/下午格式的替代方案。

LocalTime date = LocalTime.parse("15:25:08.690791");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("h B");
assertThat(date.format(formatter)).isEqualTo("3 in the afternoon");

Instead of something like “3pm“, we get an output of “3 in the afternoon“. We can also use the “B“, “BBBB“, or “BBBBBDateTimeFormatter pattern for short, full, and narrow styles respectively.

而不是像”3pm“,我们得到的输出是”3 in the afternoon“。我们还可以使用”B“、”BBBB“或”BBBBBDateTimeFormatter模式,分别用于短、全和窄样式。

4. Add Stream.toList Method (JDK-8180352)

4.增加Stream.toList方法(JDK-8180352)

The aim is to reduce the boilerplate with some commonly used Stream collectors, such as Collectors.toList and Collectors.toSet:

目的是减少一些常用的Stream收集器的模板,例如Collectors.toListCollectors.toSet

List<String> integersAsString = Arrays.asList("1", "2", "3");
List<Integer> ints = integersAsString.stream().map(Integer::parseInt).collect(Collectors.toList());
List<Integer> intsEquivalent = integersAsString.stream().map(Integer::parseInt).toList();

Our ints example works the old way, but the intsEquivalent has the same result and is more concise.

我们的ints例子按老方法工作,但intsEquivalent有同样的结果,而且更简洁。

5. Vector API Incubator (JEP-338)

5.矢量API孵化器(JEP-338)

The Vector API is in its initial incubation phase for Java 16. The idea of this API is to provide a means of vector computations that will ultimately be able to perform more optimally (on supporting CPU architectures) than the traditional scalar method of computations.

矢量API正处于Java 16的初始孵化阶段。该API的理念是提供一种矢量计算的方法,最终能够比传统的标量计算方法更优化地执行(在支持的CPU架构上)。

Let’s look at how we might traditionally multiply two arrays:

让我们看一下传统上如何将两个数组相乘。

int[] a = {1, 2, 3, 4};
int[] b = {5, 6, 7, 8};

var c = new int[a.length];

for (int i = 0; i < a.length; i++) {
    c[i] = a[i] * b[i];
}

This example of a scalar computation will, for an array of length 4, execute in 4 cycles. Now, let’s look at the equivalent vector-based computation:

这个标量计算的例子,对于一个长度为4的数组,将在4个周期内执行。现在,让我们看一下基于矢量的等效计算。

int[] a = {1, 2, 3, 4};
int[] b = {5, 6, 7, 8};

var vectorA = IntVector.fromArray(IntVector.SPECIES_128, a, 0);
var vectorB = IntVector.fromArray(IntVector.SPECIES_128, b, 0);
var vectorC = vectorA.mul(vectorB);
vectorC.intoArray(c, 0);

The first thing we do in the vector-based code is to create two IntVectors from our input arrays using the static factory method of this class fromArray. The first parameter is the size of the vector, followed by the array and the offset (here set to 0). The most important thing here is the size of the vector that we’re getting to 128 bits.  In Java, each int takes 4 bytes to hold.

在基于向量的代码中,我们做的第一件事是使用这个类的静态工厂方法fromArray,从我们的输入数组中创建两个IntVectors第一个参数是向量的大小,然后是数组和偏移量(这里设置为0)。这里最重要的是我们要得到的向量的大小为128位。 在Java中,每个int需要4个字节来保存。

Since we have an input array of 4 ints, it takes 128 bits to store. Our single Vector can store the whole array.

由于我们有一个4个int的输入数组,它需要128位来存储。我们的单个Vector可以存储整个数组。

On certain architectures, the compiler will be able to optimize the byte code to reduce the computation from 4 to only 1 cycle.  These optimizations benefit areas such as machine learning and cryptography.

在某些架构上,编译器将能够优化字节代码,将计算从4个周期减少到只有一个周期。 这些优化有利于机器学习和密码学等领域。

We should note that being in the incubation stage means this Vector API is subject to change with newer releases.

我们应该注意到,处于孵化阶段意味着这个Vector API可能会随着更新的版本而改变。

6. Records (JEP-395)

6.记录(JEP-395)

Records were introduced in Java 14. Java 16 brings some incremental changes.

记录是在Java 14中引入的。Java 16 带来了一些递增的变化

Records are similar to enums in the fact that they are a restricted form of class. Defining a record is a concise way of defining an immutable data holding object.

记录与enums类似,它们是类的一种限制形式。定义一个记录是定义一个不可变的数据持有对象的简明方式。

6.1. Example Without Records

6.1.没有记录的例子

First, let’s define a Book class:

首先,让我们定义一个Book类。

public final class Book {
    private final String title;
    private final String author;
    private final String isbn;

    public Book(String title, String author, String isbn) {
        this.title = title;
        this.author = author;
        this.isbn = isbn;
    }

    public String getTitle() {
        return title;
    }

    public String getAuthor() {
        return author;
    }

    public String getIsbn() {
        return isbn;
    }

    @Override
    public boolean equals(Object o) {
        // ...
    }

    @Override
    public int hashCode() {
        return Objects.hash(title, author, isbn);
    }
}

Creating simple data holding classes in Java requires a lot of boilerplate code. This can be cumbersome and lead to bugs where developers don’t provide all the necessary methods, such as equals and hashCode.

在Java中创建简单的数据持有类需要大量的模板代码。这可能很麻烦,而且会导致错误,因为开发人员没有提供所有必要的方法,例如equalshashCode

Similarly, sometimes developers skip the necessary steps for creating proper immutable classes. Sometimes we end up reusing a general-purpose class rather than defining a specialist one for each different use case.

同样地,有时开发人员会跳过创建适当的immutable类的必要步骤。有时我们最终会重复使用一个通用的类,而不是为每个不同的使用情况定义一个专业的类。

Most modern IDEs provide an ability to auto-generate code (such as setters, getters, constructors, etc.) that helps mitigate these issues and reduces the overhead on a developer writing the code. However, Records provide an inbuilt mechanism to reduce the boilerplate code and create the same result.

大多数现代IDE提供了自动生成代码的能力(如setters、getters、constructors等),有助于缓解这些问题,并减少开发人员编写代码的开销。然而,记录提供了一个内置的机制来减少模板代码,并创建相同的结果。

6.2. Example with Records

6.2.有记录的例子

Here is Book re-written as a Record:

这里是重新写成的记录

public record Book(String title, String author, String isbn) {
}

By using the record keyword, we have reduced the Book class to two lines. This makes it a lot easier and less error-prone.

通过使用record 关键字,我们将Book类减少到两行。这使得它变得更容易,更不容易出错。

6.3. New Additions to Records in Java 16

6.3.Java 16中对记录的新补充

With the release of Java 16, we can now define records as class members of inner classes. This is due to relaxing restrictions that were missed as part of the incremental release of Java 15 under JEP-384:

随着 Java 16 的发布,我们现在可以将记录定义为内部类的类成员。这是由于放宽了作为JEP-384下Java 15增量发布的一部分而错过的限制。

class OuterClass {
    class InnerClass {
        Book book = new Book("Title", "author", "isbn");
    }
}

7. Pattern Matching for instanceof (JEP-394)

7.instanceof的模式匹配(JEP-394)

Pattern matching for the instanceof keyword has been added as of Java 16.

instanceof关键字的模式匹配已从Java 16开始被添加。

Previously we might write code like this:

以前我们可能会写这样的代码。

Object obj = "TEST";

if (obj instanceof String) {
    String t = (String) obj;
    // do some logic...
}

Instead of purely focusing on the logic needed for the application, this code must first check the instance of obj, then cast the object to a String and assign it to a new variable t.

这段代码没有纯粹关注应用程序所需的逻辑,而是必须首先检查obj的实例,然后将该对象转换为String,并将其分配给一个新的变量t.

With the introduction of pattern matching, we can re-write this code:

随着模式匹配的引入,我们可以重写这段代码。

Object obj = "TEST";

if (obj instanceof String t) {
    // do some logic
}

We can now declare a variable – in this instance t – as part of the instanceof check.

我们现在可以声明一个变量–在这个例子中是t–作为instanceof检查的一部分。

8. Sealed Classes (JEP-397)

8.封闭式班级(JEP-397)

Sealed classes, first introduced in Java 15, provide a mechanism to determine which sub-classes are allowed to extend or implement a parent class or interface.

在Java 15中首次引入的密封类,提供了一种机制来确定哪些子类被允许扩展或实现父类或接口。

8.1. Example

8.1.例子

Let’s illustrate this by defining an interface and two implementing classes:

让我们通过定义一个接口和两个实现类来说明这一点。

public sealed interface JungleAnimal permits Monkey, Snake  {
}

public final class Monkey implements JungleAnimal {
}

public non-sealed class Snake implements JungleAnimal {
}

The sealed keyword is used in conjunction with the permits keyword to determine exactly which classes are allowed to implement this interface. In our example, this is Monkey and Snake. 

sealed关键字与permits关键字一起使用,以确定到底哪些类被允许实现这个接口。在我们的例子中,这就是MonkeySnake。MonkeySnake。

All inheriting classes of a sealed class must be marked with one of the following:

一个密封类的所有继承类必须标明以下内容之一。

  • sealed – meaning they must define what classes are permitted to inherit from it using the permits keyword.
  • final – preventing any further subclasses
  • non-sealed – allowing any class to be able to inherit from it.

A significant benefit of sealed classes is that they allow for exhaustive pattern matching checking without the need for a catch for all non-covered cases. For example, using our defined classes, we can have logic to cover all possible subclasses of JungleAnimal:

密封类的一个重要好处是,它们允许进行详尽的模式匹配检查,而不需要对所有未覆盖的情况进行捕获。例如,使用我们定义的类,我们可以有逻辑来覆盖JungleAnimal的所有可能的子类。

JungleAnimal j = // some JungleAnimal instance

if (j instanceof Monkey m) {
    // do logic
} else if (j instanceof Snake s) {
    // do logic
}

We don’t need an else block as the sealed classes only allow the two possible subtypes of Monkey and Snake.

我们不需要一个else块,因为密封类只允许MonkeySnake这两种可能的子类型。

8.2. New Additions to Sealed Classes in Java 16

8.2.Java 16中对密封类的新补充

There are a few additions to sealed classes in Java 16. These are the changes that Java 16 introduces to the sealed class:

在Java 16中,有对密封类的一些补充。这些是Java 16对密封类引入的变化。

  • The Java language recognizes sealed, non-sealed, and permits as contextual keywords (similar to abstract and extends)
  • Restrict the ability to create local classes that are subclasses of a sealed class (similar to the inability to create anonymous classes of sealed classes).
  • Stricter checks when casting sealed classes and classes derived from sealed classes

9. Other Changes

9.其他变化

Continuing from JEP-383 in the Java 15 release, the foreign linker API provides a flexible way to access native code on the host machine. Initially, for C language interoperability, in the future, it may be adaptable to other languages such as C++ or Fortran. The goal of this feature is to eventually replace the Java Native Interface.

延续Java 15版本中的JEP-383,span class=”s1″>foreign linker API提供了一种灵活的方式来访问主机上的本地代码。最初是为了C语言的互操作性,在未来,它可能适应于其他语言,如C++或Fortran。这个功能的目标是最终取代Java Native Interface

Another important change is that JDK internals is now strongly encapsulated by default. These have been accessible since Java 9. However, now the JVM requires the argument –illegal-access=permit. This will affect all libraries and apps (particularly when it comes to testing) that are currently using JDK internals directly and simply ignoring the warning messages.

另一个重要的变化是,JDK 内部结构现在默认情况下是强封装的。然而,现在JVM需要参数-illegal-access=permit。这将影响到所有目前直接使用JDK内部结构并简单地忽略警告信息的库和应用程序(特别是在涉及测试时)。

10. Conclusion

10.结语

In this article, we covered some of the features and changes introduced as part of the incremental Java 16 release. The complete list of changes in Java 16 is in the JDK release notes.

在这篇文章中,我们介绍了作为增量的 Java 16 版本的一部分而引入的一些功能和变化。Java 16 中的完整更改列表在JDK 发行说明中。

As always, all the code in this post can be found over on GitHub.

一如既往,本篇文章中的所有代码都可以在GitHub上找到