Last Updated:

Java 16 中的新功能

银狐
Java 16 徽标

 

Java 16于 2021 年 3 月 16 日正式发布,请在此处下载 Java 16

Java 16 有 17 个 JEP 项。

Java 16 开发人员功能。

jpackage、记录(标准)、模式匹配(标准)、密封类型(第二次预览)、外部内存访问 API(第三个孵化器)、替换 JNI 的外部链接器 API(孵化器)、向量 API(孵化器)

1. JEP 338:Vector API(孵化器)

Java支持自动向量化来优化算法算法,这意味着Java(JIT编译器)会自动将一些标量操作(一次一项)转换为向量操作(一次多项);但是,开发者无法控制这种向量运算的转换,它完全依赖于 JIT 编译器来优化代码,而且并非所有的标量运算都是可转换的。

此 JEP 引入了新的 Vector API,以允许开发人员显式执行向量操作。

 

2. JEP 347:启用 C++14 语言功能

此 JEP 允许在 JDK 内的 C++ 源代码中使用 [C++ 14 特性]((https://en.wikipedia.org/wiki/C%2B%2B14)。

3. JEP 357:从 Mercurial 迁移到 Git

此 JEP 将 OpenJDK 源代码从 Mercurial 迁移到 Git 或 GitHub,涉及以下JEP 369

迁移到 Git 的原因:

  1. 版本控制系统元数据 (Mercurial) 的文件太大。
  2. 可用工具
  3. 可用主机

4. JEP 369:迁移到 GitHub

该 JEP 加入了上述JEP 357,将 OpenJDK 源代码从 Mercurial 迁移到GitHub

5. JEP 376:ZGC:并发线程堆栈处理

此 JEP 通过将 ZGC 线程堆栈处理从安全点移动到并发阶段来改进 Z 垃圾收集器 (ZGC)。

历史

  • Java 11 JEP 333引入了 Z 垃圾收集器 (ZGC) 作为实验性垃圾收集器。
  • Java 15 JEP 377 中,ZGC 成为产品特性。

6. JEP 380:Unix 域套接字通道

Unix域套接字被用于相同的主机,该主机装置交换在同一主机上执行的进程之间的数据上的进程间通信(IPC)。Unix 域套接字类似于 TCP/IP 套接字,不同之处在于它们由文件系统路径名而不是 Internet 协议 (IP) 地址和端口号寻址。大多数 Unix 平台,Windows 10 和 Windows Server 2019,也支持 Unix 域套接字。

此 JEP 将Unix 域 (AF_UNIX) 套接字支持添加到现有SocketChannelServerSocketChannel

新的 Unix 域 Socket 类或 API:

  • 新的套接字地址类, java.net.UnixDomainSocketAddress
  • 新的enumjava.net.StandardProtocolFamily.UNIX

7. JEP 386:Alpine Linux 端口

这个 JEP 将 JDK 移植到Alpine Linux和其他使用musl实现的Linux 发行。这个 JDK 端口使 Java 能够在 Alpine Linux 中开箱即用,这有利于那些依赖 Java 的框架或工具,如 Tomcat 和 Spring。

PS Alpine Linux 包含小镜像,广泛应用于云部署、微服务和容器环境。

8. JEP 387:弹性元空间

Java 8 JEP 122删除了 PermGen(永久代),并在热点中引入了Metaspace,这是一个本地的堆外内存管理器。

该 JEP 通过更迅速地将未使用的 HotSpot 类元数据或元空间内存返回给操作系统来改进元空间内存管理,减少元空间占用空间并简化元空间代码。

9. JEP 388:Windows / AArch64 端口

此 JEP 将 JDK 移植到 Windows/AArch64,在 ARM 硬件、服务器或基于 ARM 的笔记本电脑上运行 JDK + Windows。

PS Windows/AArch64 是终端用户市场的热门需求。

10. JEP 389:外部链接器 API(孵化器)

此 JEP 使 Java 代码能够调用或可由其他语言(如 C 或 C++)编写的本机代码调用,替换Java 本机接口 (JNI)

PS 这是一个孵化功能;需要添加--add-modules jdk.incubator.foreign来编译和运行 Java 代码。

10.1 下面的例子展示了如何使用外部链接器 API 调用标准 C 库strlen来返回字符串的长度。


size_t strlen(const char *s);
CStrLen.java

import jdk.incubator.foreign.*;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;

import static jdk.incubator.foreign.CLinker.C_LONG;
import static jdk.incubator.foreign.CLinker.C_POINTER;

// Java call standard C library
// size_t strlen(const char *s);
public class CStrLen {

  public static void main(String[] args) throws Throwable {

      if (args.length == 0) {
          throw new IllegalArgumentException("Please provide an argument.");
      }

      String input = args[0];

      MethodHandle strlen = CLinker.getInstance().downcallHandle(
              LibraryLookup.ofDefault().lookup("strlen").get(),
              MethodType.methodType(long.class, MemoryAddress.class),
              FunctionDescriptor.of(C_LONG, C_POINTER)
      );

      try (MemorySegment str = CLinker.toCString(input)) {
          long len = (long) strlen.invokeExact(str.address()); // 5
          System.out.println(len);
      }

  }
}

编译时启用孵化器模块。

终端

$ javac --add-modules jdk.incubator.foreign CStrLen.java

warning: using incubating module(s): jdk.incubator.foreign
1 warning

在启用孵化器模块的情况下运行。

终端

$ java --add-modules jdk.incubator.foreign -Dforeign.restricted=permit CStrLen mkyong

WARNING: Using incubator modules: jdk.incubator.foreign
6

10.2 下面是调用在 C 代码中定义的函数的另一个示例。

一个简单的 C 函数来打印一个 hello world。


#include <stdio.h>

void printHello() {
        printf("hello world!\n");
}

编译上述 C 代码并输出到共享库hello.so

终端

$ gcc -c -fPIC hello.c
$ gcc -shared -o hello.so hello.o

在 Java 程序下方,找到hello.so并调用其方法printHello

JEP389.java

import jdk.incubator.foreign.CLinker;
import jdk.incubator.foreign.FunctionDescriptor;
import jdk.incubator.foreign.LibraryLookup;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.nio.file.Path;
import java.util.Optional;

public class JEP389 {

  public static void main(String[] args) throws Throwable {

      Path path = Path.of("/home/mkyong/projects/core-java/java-16/hello.so");

      LibraryLookup libraryLookup = LibraryLookup.ofPath(path);

      Optional<LibraryLookup.Symbol> optionalSymbol = libraryLookup.lookup("printHello");
      if (optionalSymbol.isPresent()) {

          LibraryLookup.Symbol symbol = optionalSymbol.get();

          FunctionDescriptor functionDescriptor = FunctionDescriptor.ofVoid();

          MethodType methodType = MethodType.methodType(Void.TYPE);

          MethodHandle methodHandle = CLinker.getInstance().downcallHandle(
                  symbol.address(),
                  methodType,
                  functionDescriptor);
          methodHandle.invokeExact();

      }

  }
}

编译。

终端

$ javac --add-modules jdk.incubator.foreign JEP389.java

warning: using incubating module(s): jdk.incubator.foreign
1 warning

跑。

终端

$ java --add-modules jdk.incubator.foreign -Dforeign.restricted=permit JEP389

WARNING: Using incubator modules: jdk.incubator.foreign
hello world!

历史

  • Java 14 JEP 370引入了外部内存访问 API(孵化器)。
  • Java 15 JEP 383引入了外部内存访问 API(第二孵化器)。
  • Java 16 JEP 389引入了外部链接器 API(孵化器)。
  • Java 16 JEP 393引入了外部内存访问 API(第三孵化器)。
  • Java 17 JEP 412引入了外部函数和内存 API(孵化器)。

11. JEP 390:基于值的类的警告

如果我们同步基于值的类的实例,这个 JEP 会提供一个新的警告;还弃用原始包装类(基于值的)构造函数以进行删除。

11.1 如何识别基于值的类?
注释@jdk.internal.ValueBased告诉我们一个类是否是一个基于值的类。查看以下两个类,我们可以知道原始包装类DoubleOptional基于值的类。

Optional.java

package java.util;

@jdk.internal.ValueBased
public final class Optional<T> {
  //...
}
Double.java

package java.lang;

@jdk.internal.ValueBased
public final class Double extends Number
        implements Comparable<Double>, Constable, ConstantDesc {

  //...
}

11.2 下面的例子尝试synchronized一个基于值的类。

JEP390.java

public class JEP390 {

    public static void main(String[] args) {

        Double d = 20.0;
        synchronized (d) {} // javac warning & HotSpot warning
    }
}

编译上面的代码并点击新的警告。

终端

$ javac JEP390.java
JEP390.java:7: warning: [synchronization] attempt to synchronize on an instance of a value-based class
        synchronized (d) {} // javac warning & HotSpot warning
        ^
1 warning

11.3 Java 9 弃用了原始包装类(基于值)构造函数,现在标记为删除。

Double.java

package java.lang;

//...

@jdk.internal.ValueBased
public final class Double extends Number
        implements Comparable<Double>, Constable, ConstantDesc {

  @Deprecated(since="9", forRemoval = true)
  public Double(double value) {
      this.value = value;
  }

  @Deprecated(since="9", forRemoval = true)
  public Double(String s) throws NumberFormatException {
      value = parseDouble(s);
  }

  //...
}

进一步阅读

这是关于未来版本中的值类型吗?

12. JEP 392:打包工具

JEP 将该jpackage工具从jdk.incubator.jpackage移至jdk.jpackage,并成为 Java 16 中的标准或产品功能。这jpackage是一个打包工具,用于将 Java 应用程序打包到特定于平台的包中,例如:

  • Linux:deb 和 rpm
  • macOS:pkg 和 dmg
  • Windows:msi 和 exe

历史

  • Java 14 JEP 343引入了一个jpackage孵化工具,它在 Java 15 中仍然是一个孵化工具。

12.1 下面的例子展示了jpackage如何deb在Linux系统(Ubuntu)中将一个简单的Java Swing程序打包成格式。

一个简单的 Java Swing 程序,用于显示 hello world。

JEP392.java

import javax.swing.*;
import java.awt.*;

public class JEP392 {

    public static void main(String[] args) {

        JFrame frame = new JFrame("Hello World Java Swing");
        // display frame site
        frame.setMinimumSize(new Dimension(800, 600));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // center the JLabel
        JLabel lblText = new JLabel("Hello World!", SwingConstants.CENTER);
        // add JLabel to JFrame
        frame.getContentPane().add(lblText);

        // display it
        frame.pack();
        frame.setVisible(true);

    }
}

创建一个jar文件并将jpackage其放入特定于平台的包中;因为这是在 Ubuntu 系统中测试的,所以它会创建一个.deb文件。

终端

# compile
$ javac JEP392.java

# create a jar file
$ jar cvf hello.jar JEP392.class

# package the jar file into platform-specific package
$ /opt/jdk-16/bin/jpackage -i . -n JEP392 --main-jar hello.jar --main-class JEP392

# The jpackage created this jep392_1.0-1_amd64.deb
$ ls -lsah
4.0K -rw-rw-r--  1 mkyong mkyong  994 Mac  15 13:52 hello.jar
 30M -rw-r--r--  1 mkyong mkyong  30M Mac  15 14:01 jep392_1.0-1_amd64.deb

安装 .deb 文件

终端

sudo dpkg -i jep392_1.0-1_amd64.deb
Selecting previously unselected package jep392.
(Reading database ... 225576 files and directories currently installed.)
Preparing to unpack jep392_1.0-1_amd64.deb ...
Unpacking jep392 (1.0-1) ...
Setting up jep392 (1.0-1) ...

$ ls -lsah /opt/jep392/bin/JEP392
1.6M -rwxr-xr-x 1 root root 1.6M Mac  15 14:01 /opt/jep392/bin/JEP392

默认安装目录在

  • Linux是 /opt
  • macOS 是 /Applications
  • 窗户是 C:\Program Files\

PS 这可以通过jpackage --install-dir选项覆盖。

运行已安装的 Java Swing 程序。

终端

$ /opt/jep392/bin/JEP392

输出

你好世界秋千

 

jpackage -h永远是你最好的朋友:

终端

$ /opt/jdk-16/bin/jpackage -h
Usage: jpackage <options>

Sample usages:
--------------
  Generate an application package suitable for the host system:
      For a modular application:
          jpackage -n name -p modulePath -m moduleName/className
      For a non-modular application:
          jpackage -i inputDir -n name \
              --main-class className --main-jar myJar.jar
      From a pre-built application image:
          jpackage -n name --app-image appImageDir
  Generate an application image:
      For a modular application:
          jpackage --type app-image -n name -p modulePath \
              -m moduleName/className
      For a non-modular application:
          jpackage --type app-image -i inputDir -n name \
              --main-class className --main-jar myJar.jar
      To provide your own options to jlink, run jlink separately:
          jlink --output appRuntimeImage -p modulePath -m moduleName \
              --no-header-files [<additional jlink options>...]
          jpackage --type app-image -n name \
              -m moduleName/className --runtime-image appRuntimeImage
  Generate a Java runtime package:
      jpackage -n name --runtime-image <runtime-image>

  //...

进一步阅读

13. JEP 393:外内存访问 API(第三孵化器)

外部内存访问 API 允许 Java API 访问 Java 堆之外的外部内存,例如memcachedLucene等。

此 JEP 更新了外部内存访问 API 并保留为孵化器模块

历史

  • Java 14 JEP 370引入了外部内存访问 API(孵化器)。
  • Java 15 JEP 383引入了外部内存访问 API(第二孵化器)。
  • Java 16 JEP 389引入了外部链接器 API(孵化器)。
  • Java 16 JEP 393引入了外部内存访问 API(第三孵化器)。
  • Java 17 JEP 412引入了外部函数和内存 API(孵化器)。

14. JEP 394:instanceof 的模式匹配

模式匹配instanceof是 Java 16 中的标准或产品特性。

在模式匹配之前,我们检查对象的类型并手动转换为变量。


if (obj instanceof String) {
    String s = (String) obj;    // cast
}

现在我们可以检查对象的类型并自动转换它


if (obj instanceof String s) {
    //... s is a string
}

例如,下面是一个常见的检查和转换示例。


  if (obj instanceof String) {
      String s = (String) obj;
      if (s.length() > 5) {
          if (s.equalsIgnoreCase("java16")) {
              //...
          }
      }
  }

而且,我们可以使用新的instanceof.


  if (obj instanceof String s && s.length() > 5) {
      if (s.equalsIgnoreCase("java16")) {
          //...
      }
  }

历史

  • Java 14 JEP 305,第一次预览。
  • Java 15 JEP 375,第二次预览。
  • Java 16,标准功能。

15. JEP 395:记录

record定稿,并成为一个标准功能。

JEP395.java

package com.mkyong.java16.jep395;

public class JEP395 {

    record Point(int x, int y) { }

    public static void main(String[] args) {

        Point p1 = new Point(10, 20);
        System.out.println(p1.x());         // 10
        System.out.println(p1.y());         // 20

        Point p2 = new Point(11, 22);
        System.out.println(p2.x());         // 11
        System.out.println(p2.y());         // 22

        Point p3 = new Point(10, 20);
        System.out.println(p3.x());         // 10
        System.out.println(p3.y());         // 20

        System.out.println(p1.hashCode());  // 330
        System.out.println(p2.hashCode());  // 363
        System.out.println(p3.hashCode());  // 330

        System.out.println(p1.equals(p2));  // false
        System.out.println(p1.equals(p3));  // true
        System.out.println(p2.equals(p3));  // false

    }
}

历史

  • Java 14 JEP 359,第一次预览。
  • Java 15 JEP 384,第二次预览。
  • Java 16,标准功能。

进一步阅读

16. JEP 396:默认强封装JDK内部

Java 9 JEP 261引入了--illegal-access控制内部 API 访问和 JDK 打包的选项。

此 JEP 将--illegal-access选项的默认模式从允许更改为拒绝。通过此更改,JDK的内部包和 API(关键内部 API除外)将不再默认打开。

该 JEP 的动机是阻止第三方库、框架和工具使用 JDK 的内部 API 和包。

17. JEP 397:密封类(第二次预览)

Java 15 JEP 360引入了密封类和接口来限制哪些类可以扩展或实现它们。这个 JEP 是第二次预览,有一些改进。

PS 这个密封类是Java 17 中的标准特性。

下载源代码

$ git clone https://github.com/mkyong/core-java

$ cd java-16

参考