Java16的主要新特性总结

453 阅读29分钟

概述

JDK 16 于 2021 年 3 月 16 日正式发布。

JEP(Java Enhancement Proposal)Java增强提案

CSR(Compatibility & Specification Review) 兼容性和规范审查

变动说明

官网:

docs.oracle.com/en/java/jav…

www.oracle.com/java/techno…

openjdk.org/projects/jd…

更多参考:

docs.oracle.com/en/java/jav… 更多版本:docs.oracle.com/en/java/jav…

docs.oracle.com/en/java/jav…

重要变更和信息

JDK 16 包含 17 个 新特性 ,分别为:

而其中与开发过程中直接相关的特性主要包括:JEP 394(instanceof的模式匹配 (正式特性))、JEP 395(Record类)、JEP 397(密封类(第二次预览))等。

下载地址

您可以从这个链接下载生产就绪的OpenJDK版本。文件为压缩包,解压并设置环境变量就可以使用。

当然你也可以从这个链接下载Oracle JDK版本(但是需要注意商用限制),更多版本下载

Java16新特性总结

1、JEP 395:Record类(正式特性)

JEP 395 (tools/javac)

功能进化

Java版本特性类型JEP特性
Java 14预览特性JEP 359引入Record类作为预览特性
Java 15预览特性JEP 384修正及优化,语法上同上一版没有区别
Java 16正式特性JEP 395成为正式特性

在JDK14中,引入了一个新类java.lang.Record。这是一种新的类型声明。Records 允许我们以一种简洁的方式定义一个类,我们只需要指定其数据内容。对于每个Record类,Java 都会自动地为其成员变量生成 equals(), hashCode(), toString() 方法,以及所有字段的访问器方法(getter),为什么没有 setter方法呢?因为Record的实例是不可变的,它所有的字段都是 final 的,这就意味着一旦构造了一个Record实例,其状态就不能更改了。

与枚举一样,记录也是类的受限形式。它非常适合作为“数据载体”,即包含不想更改的数据的类,以及只包含最基本的方法(如构造函数和访问器)的类。

与前面介绍的其他预览特性一样,这个预览特性也顺应了减少Java冗余代码的趋势,能帮助开发者编写更精炼的代码。

一个示例类

定义一个长方形类

 final class Rectangle implements Shape {
     final double length;
     final double width;
     
     public Rectangle(double length, double width) {
         this.length = length;
         this.width = width;
     }
     
     double length() { return length; }
     double width() { return width; }
 }

它具有以下特点:

  • 所有字段都是final的
  • 只包含构造器:Rectangle(double length, double width)和2个访问器方法:length() 和width()

您可以用record表示此类:

 record Rectangle(float length, float width) { }

一个record由一个类名称(在本例中为Rectangle)和一个record属性列表(在本示例中为float length和float width)组成。

record会自动生成以下内容:

  • 为每个属性生成一个private final的字段
  • 为每个属性生成一个与组件名相同的访问方法;在本例中,这些方法是Rectangle::length()和Rectangle::width()
  • 一个公开的构造函数,参数包括所有属性。构造函数的参数与字段对应。
  • equals()和hashCode()方法的实现,如果两个record类型相同并且属性值相等,那么它们是相等的
  • toString()方法的实现,包括所有字段名和他们的值。

紧凑型构造函数

如果你想在record自定义一个构造函数。那么注意,它与普通的类构造函数不同,record的构造函数没有参数列表:这被称为紧凑型构造函数。

例如,下面的record``HelloWorld有一个字段message。它的自定义构造函数调用Objects.requireNonNull(message),如果message字段是用null值初始化的,则抛出NullPointerException。(自定义记录构造函数仍然会初始化所有字段)

 record HelloWorld(String message) {
     public HelloWorld {
         java.util.Objects.requireNonNull(message);
     }
 }

测试代码:

 @Test
 public void test() {
     HelloWorld h1 = new HelloWorld(null); // new HelloWorld("天地玄黄宇宙洪荒"); //用这个测试,可以发现字段还是会初始化的
     System.out.println(h1);
 }

这个测试代码执行报java.lang.NullPointerException异常。

使用限制

以下是record类使用的限制:

  • Record类不能继承任何类
  • Record类不能声明实例字段(与record组件相对应的 private final字段除外);任何其他声明的字段都必须是静态的
  • Record类不能是抽象的;它是final的
  • Record类的成员变量是final的

除了这些限制之外,record类的行为类似于常规类:

  • 可以在类中声明record;嵌套record是static的
  • record可以实现接口
  • 使用new关键字实例化record
  • 您可以在record的主体中声明静态方法、静态字段、静态初始值设定项、构造函数、实例方法和嵌套类型
  • 可以对record和record的属性进行注释

与record相关的API

java.lang.Class类有2个方法与record相关:

  • RecordComponent[] 返回类型getRecordComponents(): 返回record的所有字段列表。

  • boolean isRecord(): 与isEnum()类似,如果是record则返回true。

2、JEP 394:模式匹配的 instanceof(正式特性)

JEP 394 (tools/javac)

功能进化

Java版本特性类型JEP特性
Java 14预览特性JEP 305引入instanceof模式匹配为预览特性
Java 15预览特性JEP 375修正及优化,语法上同上一版没有区别
Java 16正式特性JEP 394成为正式特性

在Java 14之前,instanceof主要用来检查对象的类型,检查匹配后,还需要对其进行类型强转才能使用该类型的变量,这显得很多余也很业余,而且存在手滑转错类型的可能。

Java SE 14为instanceof操作符引入了模式匹配;如果instanceof运算符的结果为true,则判断对象将自动绑定到声明的变量上。

在Java 14 之前的代码写法:

 if (obj instanceof String) {
     String s = (String) obj;
     // 业务逻辑
 }

在 Java 14 中,使用模式匹配的 instanceof,这可以被简化为:

 if (obj instanceof String s) {
     // 直接使用 s
 }

如果 obj 是 String 类型的实例,s 就会被自动声明并初始化为 obj 强制转换后的值。这样就避免了单独的类型转换步骤,并使得代码更加简洁易读。

 @Test
 public void test() {
     Object name = "初唐四杰";
     // 旧写法
     if (name instanceof String) {
         String s = (String) name;
         // 业务逻辑
         System.out.println(s);
     }
 ​
     // 新写法
     if (name instanceof String s) {
         // 直接使用 s
         System.out.println(s);
     }    
 }

作用域

绑定变量的作用域一般是在instanceof内,但是在适当的条件下也可以扩展到外部

 @Test
 public void test() {
     Object name = "初唐四杰";    
     if (!(name instanceof Integer i)) {
         // 是为是!处理,不能直接使用 i,否则编译报错
         // System.out.println(i);
         return;
     }
     // 经过前面的return处理,i一定存在。这里可以使用i,你可以尝试将前面的return语句注释掉,看看,这里编译就报错了。因为情况变的不确定了。
     System.out.println(i);
 }

与运算符结合

// 这个语句可以运行,因为&&是短路运算符,前面满足条件才会走到后面
if (name instanceof Integer in && in > 10) {
    // 处理
}

下面的语句编译不通过

// 这个语句编译会报错,因为in不一定存在
if (name instanceof Integer || in > 10) {
	// 处理
}

3、JEP 397:密封的类和接口(第二次预览)

JEP 397

这仍是一个预览特性。

功能进化

Java版本特性类型JEP特性
Java 15预览特性JEP 360引入了密封类作为预览特性。
Java 16预览特性JEP 397第二次预览

密封的类和接口限制了哪些其他类或接口可以扩展或实现它们。

继承,作为面向对象语言的三大特性之一,我们工作过程中经常使用,可以重写父类的方法。

通常开发项目时,我们会先将接口提供出来,然后根据情况给出不同的基础实现类,子类再基础这些基础实现类进行扩展,我们可能并不希望子类直接继承接口,当然直接继承接口的写法从代码上看没有任何问题,但存在安全隐患。一般我们会通过开发约束对,这样的情况说一些要求,但是这样并不能杜绝这类问题。

密封类

为了进一步增强继承的限制能力,Java 15 引入密封类来精确控制类的继承问题 ,目前版本为预览特性。

什么是密封类

密封类的主要目的是提供一种更加精确地控制类继承的方法,通过这种方式,类的设计者可以指定一个类它能够被哪些类继承,它增强了类的封装性和安全性。由于密封类限制了类的继承,所以它使得代码更加可预测和易于维护。

  • 密封类(接口)用 sealed 修饰,则它的所有子类都必须在同一个模块或者包内,并且这些子类必须被显式地声明为该密封类的直接子类。
  • 密封类(接口)的子类可以被声明为non-sealed(非密封的)或final(最终的)。non-sealed的子类可以被进一步继承,而final的子类则不能。
  • 密封类(接口)使用 permits 来指定它的子类。

示例代码

这里我们以诗人为例,简化一下,我们这里只讨论汉朝诗人、唐朝诗人、宋朝诗人,代码如下:

// 诗人基类
public class Poet {
}

// 汉朝诗人
public class HanPoet extends Hero{
}
// 唐朝诗人
public class TangPoet extends Poet{
}

// 宋朝诗人
public class SongPoet extends Hero{
}

接下来我们每个类别下面定义2个诗人:

  • 汉朝诗人(HanPoet):司马相如(SiMaXiangRu)、班固(BanGu)、
  • 唐朝诗人(TangPoet):李白(Libai)、杜甫(DuFu)
  • 宋朝诗人(SongPoet):苏轼(SuShi)、陆游(LuYou)

其中李白(Libai)继承自唐朝诗人(TangPoet),我们可以为唐朝诗人做一些公共处理,比如朝代是唐朝,但是有没有这种可能,有程序猿把李白的父类定义为诗人(Poet),那么我们为唐朝诗人定义的那些处理,李白就需要全部重新实现。这显然破坏了继承的实用性。

  • 使用 sealed 修饰Poet, permits 限定子类为: HanPoet 、TangPoet、SongPoet ,只允许这3个类继承,如下:
// 英雄基类,限制子类为:汉朝诗人(HanPoet)、唐朝诗人(TangPoet)、宋朝诗人(SongPoet)
public sealed class Poet permits HanPoet,TangPoet,SongPoet {
}
  • 第二层基类,继续使用 sealed 修饰
// 汉朝诗人,限制子类为:司马相如(SiMaXiangRu)、班固(BanGu)
public sealed class HanPoet extends Hero permits SiMaXiangRu,BanGu{
}

// 唐朝诗人,限制子类为:李白(Libai)、杜甫(DuFu)
public sealed class TangPoet extends Hero permits Libai,DuFu{
}

// 宋朝诗人,限制子类为:苏轼(SuShi)、陆游(LuYou)
public sealed class SongPoet extends Hero permits SuShi,LuYou{
}
  • 第三层为具体诗人,他们继承第二层的诗人类型,使用extends继承即可,同时需要表示为non-sealed或final,由于我们不希望类再往下了,所以定义为 final:
public final class SiMaXiangRu extends HanPoet{
}

public final class Libai extends TangPoet{
}

public final class SuShi extends SongPoet{
}

这样,子类就不能随便继承父类了。

core-libs/java.util.stream

4、细微改动

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

java.util.Stream中添加了一个新的方法toList。可以就可以不使用stream.collect(Collectors.toList())来转成List了。生成的是unmodifiableList,不可修改。

@Test
public void test() {
    // 出自《笠翁对韵》,与《声律启蒙》、和《训蒙骈句》合称吟诗作对三基。
    String str = "天对地,雨对风。大陆对长空。山花对海树,赤日对苍穹。雷隐隐,雾蒙蒙。日下对天中。风高秋月白,雨霁晚霞红。牛女二星河左右,参商两曜斗西东。十月塞边,飒飒寒霜惊戍旅;三冬江上,漫漫朔雪冷渔翁。"
        + "河对汉,绿对红。雨伯对雷公。烟楼对雪洞,月殿对天宫。云叆叇,日曈朦。腊屐对渔蓬。过天星似箭,吐魄月如弓。驿旅客逢梅子雨,池亭人挹藕花风。茅店村前,皓月坠林鸡唱韵;板桥路上,青霜锁道马行踪。"
        + "山对海,华对嵩。四岳对三公。宫花对禁柳,塞雁对江龙。清暑殿,广寒宫。拾翠对题红。庄周梦化蝶,吕望兆飞熊。北牖当风停夏扇,南檐曝日省冬烘。鹤舞楼头,玉笛弄残仙子月;凤翔台上,紫箫吹断美人风。";

    List<String> list = Arrays.asList(str.split("。"));

    List<String> result = list.stream().filter(s -> s.contains("对")).filter(e -> e.length() > 5).toList();

    System.out.printf("list 长度:%s 数据:%s%n", result.size(), result);
}

执行结果

list 长度:6 数据:[天对地,雨对风, 山花对海树,赤日对苍穹, 河对汉,绿对红, 烟楼对雪洞,月殿对天宫, 山对海,华对嵩, 宫花对禁柳,塞雁对江龙]

Stream.toList()和stream.collect(Collectors.toList())的区别

Stream.toList()返回是一个unmodifiableList不可变的List,而使用Stream.collect(Collectors.toList())返回的是一个普通的List,是可以做增删改操作的。

(2)java.time包的格式化支持一天中的数据段(JDK-8180352

日期格式新增字母 B 表示一天中的时间段(day period)(转换规则按unicode.org/reports/tr3…中的规定),在类 java.time.format.DateTimeFormatter/DateTimeFormatterBuilder 中提供了支持,可以表示一天中的时间段,如"in the morning"(上午)或"at night"(晚上),而不仅仅是am/pm。

// 时间是 13:45,输出:下午,其他还有上午、晚上等
DateTimeFormatter.ofPattern("B").format(LocalTime.now())

(3)HttpClient的默认实现返回可取消的Future对象(JDK-8245462

默认的HttpClient是通过调用HttpClient.newHttpClient()或调用HttpClient.newBuilder()返回的构建器再调用build方法创建的。默认HttpClient中sendAsync方法的实现现在返回可取消的CompletableFuture对象。对未完成的可取消未来调用cancel(true),会尝试取消HTTP请求,以尽快释放底层资源。可以参考HttpClient::sendAsync方法的API文档。

上面创建HttpClient的方法可能抛出UncheckedIOException异常(JDK-8248006)。

(4)修正Path.of或Paths.get的第一个参数为null时不会抛出空指针异常的问题(JDK-8254876

之前的版本中,Path.of()和Paths.get()方法的参数有多个时,第一个参数并没有进行不能为null的检查。在这个版本中,如果第一个参数为null则会跟其他参数为null一样抛出NullPointerException。

5、JEP 338:向量 API(孵化器特性)

JEP 338 (hotspot/compiler)

提供孵化模块jdk.incultor.vector的初始迭代,以表示在运行时可靠地编译为支持的CPU架构上的最佳向量硬件指令的向量计算,从而实现优于等效标量计算的性能。

6、JEP 347:启用 C++14 语言特性

JEP 347

JDK 的某些组件,特别是 Java 虚拟机(JVM),是采用 C++ 语言开发的。随着 C++ 的不断演进,新标准的推出带来了诸多有益的特性,比如改进的类型推断、智能指针和 lambda 表达式等,这些特性对于提升代码品质和开发效率都有显著作用。然而,JDK 的开发过程中主要还停留在使用较为旧版 C++ 标准(例如 C++98),这限制了可利用的语言功能的广度。

随着 Java 16 对 C++14 的支持,这象征着 JDK 开发者在编写 JDK 源码时可以借助 C++14 的标准特性,从而有望提高 JDK 的开发效率和代码质量。

7、JEP 357:将JDK的源代码库从Mercurial迁移到Git

JEP 357

8、JEP 369:将JDK的源代码库托管到GitHub

JEP 369

9、JEP 376:ZGC 并发线程处理

JEP 376 (hotspot/gc)

ZGC 是 Java 11 中引入的一个实验性垃圾收集器,设计目标是实现低延迟一个可伸缩的、低延迟的垃圾收集器。在 Java 11 到 Java 15 期间,ZGC 已经表现出了卓越的低延迟特性,但在某些情况下,它需要暂停应用线程来处理堆中的引用。

Java 16 引入并发线程处理,旨在将这些暂停转变为并发处理,以进一步降低延迟。该特性的核心是将 ZGC 中的最后一块堆管理工作 — 引用处理(Reference Processing) — 转变为并发执行。

功能进化

java版本特性类型JEP特性
Java 11预览特性JEP 333引入 ZGC 作为实验特性
Java 15正式特性JEP 377成为正式特性
Java 16正式特性JEP 376并发线程处理

10、JEP 380:Unix-Domain 套接字通道

JEP 380 (core-libs/java.nio)

在java.nio.channels、SocketChannel和ServerSocketChannel类中提供对Unix域套接字(AF_Unix)的支持。

11、JEP 386:AlpineLinux 移植

JEP 386

12、JEP 387:弹性元空间

JEP 387 (hotspot/runtime)

在 Java 8 中,元空间替代了原有的永久代(PermGen),成为存储类元数据的区域。虽然这一改变解决了永久代大小限制的问题,但元空间的内存管理依然不够高效,特别是在类卸载后释放内存方面。Java 16 引入弹性元空间,目的是进一步提高元空间的内存使用效率,特别是在动态加载和卸载类的应用场景中。

弹性元空间彻底检修了VM内部元空间和类空间的实现。用于类元数据的内存较少。节省的效果在涉及大量小粒度类加载器的场景中最为明显。类卸载后,内存会及时返回到操作系统。

添加了一个开关来微调元空间回收:-XX:MetaspaceReclaimPolicy=(balanced|aggressive|none),默认是balanced,使虚拟机回收内存,同时将计算开销保持在最低限度;激进地适度提高回收率,代价是记账成本略高;没有一个完全关闭回收。

开关InitialBootClassLoaderMetaspaceSize和UseLargePagesInMetaspace已被弃用。

13、JEP 388:Windows/AArch64 移植

JEP 388

将JDK移植到Windows/AArch64。

14、JEP 389:外部函数与内存 API(孵化特性)

JEP 389 (core-libs)

孵化阶段。

功能进化

java版本特性类型JEP特性
Java 14孵化特性JEP 370引入了外部内存访问 API作为孵化特性
Java 15第二次孵化JEP 383优化外部内存访问 API
Java 16孵化特性JEP 389引入了外部链接器 API
Java 16第三次孵化JEP 393功能优化

引入一个API,它提供对本机代码的静态类型的纯Java访问。此API与Foreign-Memory API(JEP 393)一起,将大大简化绑定到本机库的错误处理过程。

15、JEP 390:对基于值的类发出警告

JEP 390 (core-libs)

Java 16 对@jdk.internal.ValueBased注解加入了基于值的类的告警。

将基础类的包装类指定为基于值的,弃用其构造函数以表示降落会删除(Java 9注解@Deprecated得到了增强,增加了 since 和 forRemoval 两个属性,可以分别指定一个程序元素被废弃的版本,以及是否会在今后的版本中被删除。),从而提示新的弃用警告。Java平台中任何基于值的类的实例上进行Synchronized操作,编译时将产生警告。

Java 16引入该特性的主要目的是警告开发者关于基于值的类的使用。基于值的类是一种特殊类型的类,它们的实例是不可变的。应该当成,单纯的数据载体,不应该被视为类的实例。

当我们尝试进行同步(使用synchronized关键字)时,编译器会发出警告,比如:

public void test1(Integer count) {
    for (int i = 0; i < 10; i++) {
        new Thread(() -> {
            synchronized (count) { // 这里会产生编译告警
                
            }
        }).start();
    }
}

该特性通过告警的方式,引导开发者更好地理解和利用基于值的类,为未来 Java 版本中可能的优化和特性变化做好准备。

警告信息:

Integer is a value-based type which is a discouraged argument for the synchronized statement

16、JEP 392:打包工具(正式特性)

JEP 392 (tools/jpackage)

功能进化

java版本特性类型JEP特性
Java 14孵化特性JEP 343引入了打包工具孵化特性
Java 16正式特性JEP 392成为正式特性

在Java 14之前,Java 应用一般使用JAR包的形式进行分发和运行,或者需要第三方工具来创建本地应用程序包,Java 14引入一个新的打包工具:基于 javapackager的jpackage,用于打包Java应用程序为特定平台的本地安装包。但是在Java 14中它是作为一个孵化特性引入的,在Java 16中作为正式特性发布。

前置条件

事先准备

  • JAVA 16版本以上
  • Windows系统下使用jpackage指令需要用到WiX,版本3.0及以上,并配置好PATH变量;
  • java程序提前打包成jar文件

如果没有安装WiX,使用jpackage打包时会提示:

添加图片注释,不超过 140 字(可选)

安装.Net Framework 3

WiX依赖于.Net Framework 3,所以如果之前没有安装,那么在装Wix之前需要先安装.Net Framework 3。

安装步骤: 1、控制面板的“程序和功能”中点击“启用和关闭Windows功能” 2、勾选.NET Framework 3.5(包括.NET 2.0 和 3.0),点击确定,再点击同意下载 3、等待自动下载并安装完成 4、最后重新启动计算机

添加图片注释,不超过 140 字(可选)

安装WiX

下载地址:wixtoolset.org/docs/wix3/

下载完成后,点击exe文件,选择Install。等待安装完成。

添加图片注释,不超过 140 字(可选)

出现Complete即完成。

添加图片注释,不超过 140 字(可选)

默认安装位置:C:\Program Files (x86)\WiX Toolset v3.14 配置环境变量:在Path中增加C:\Program Files (x86)\WiX Toolset v3.14\bin。

添加图片注释,不超过 140 字(可选)

1、生成运行程序:
jpackage --type app-image --input [Jar包所在文件夹] --runtime-image [Jre文件夹] --name [应用名称] --main-jar [可执行Jar包] --icon [程序图标的路径] --app-version [版本号] --vendor [程序供应商的名称] --copyright [版权信息] --description [应用描述] --dest [输出目录]
2、生成安装程序:
jpackage --type msi --win-dir-chooser --name [安装程序的名称] --app-image [运行程序的文件夹] --dest [输出目录]
"F:\Program Files\Java\jdk-21.0.2\bin\jlink" --add-modules java.base,java.desktop --output myjre
"F:\Program Files\Java\jdk-21.0.2\bin\jpackage" --name myapp --runtime-image myjre --input . --main-jar mytest-pkg-1.0.0-SNAPSHOT.jar

"F:\Program Files\Java\jdk-21.0.2\bin\jpackage" --name myapp --runtime-image myjre --input . --main-jar mytest-pkg-1.0.0-SNAPSHOT.jar \
--win-dir-chooser --win-menu --win-per-user-install --win-shortcut --win-shortcut-prompt

添加图片注释,不超过 140 字(可选)

jpackage -h 输出帮助信息

用法:jpackage <options>

示例用法:
--------------
    生成适合主机系统的应用程序包:
        对于模块化应用程序:
            jpackage -n name -p modulePath -m moduleName/className
        对于非模块化应用程序:
            jpackage -i inputDir -n name \
                --main-class className --main-jar myJar.jar
        从预构建的应用程序映像:
            jpackage -n name --app-image appImageDir
    生成应用程序映像:
        对于模块化应用程序:
            jpackage --type app-image -n name -p modulePath \
                -m moduleName/className
        对于非模块化应用程序:
            jpackage --type app-image -i inputDir -n name \
                --main-class className --main-jar myJar.jar
        要为 jlink 提供您自己的选项,请单独运行 jlink:
            jlink --output appRuntimeImage -p modulePath \
                --add-modules moduleName \
                --no-header-files [<additional jlink options>...]
            jpackage --type app-image -n name \
                -m moduleName/className --runtime-image appRuntimeImage
    生成 Java 运行时程序包:
        jpackage -n name --runtime-image <runtime-image>

一般选项:
  @<filename>
          从文件读取选项和/或模式
          可以多次使用此选项。
  --type -t <type>
          要创建的程序包的类型
          有效值为:{"app-image", "exe", "msi"}
          如果未指定此选项,则将创建与平台相关的
          默认类型。
  --app-version <version>
          应用程序和/或程序包的版本
  --copyright <copyright string>
          应用程序的版权
  --description <description string>
          应用程序的说明
  --help -h
          将用法文本输出到输出流并退出,用法文本中包含
          适用于当前平台的每个有效选项的列表和说明
  --icon <file path>
          应用程序包图标的路径
          (绝对路径或相对于当前目录的路径)
  --name -n <name>
          应用程序和/或程序包的名称
  --dest -d <destination path>
          用来放置所生成的输出文件的路径
          (绝对路径或相对于当前目录的路径)
          默认为当前的工作目录。
  --temp <directory path>
          用来创建临时文件的新目录或空白目录的路径
          (绝对路径或相对于当前目录的路径)
          如果指定,则在任务完成时将不删除临时目录,
          必须手动删除临时目录。
          如果未指定,则将创建一个临时目录,
          并在任务完成时删除该临时目录。
  --vendor <vendor string>
          应用程序的供应商
  --verbose
          启用详细的输出
  --version
          将产品版本输出到输出流并退出。

用来创建运行时映像的选项:
  --add-modules <模块名称>[,<模块名称>...]
          要添加的模块的逗号 (",") 分隔列表
          此模块列表连同主模块(如果指定)
          将作为 --add-module 参数传递到 jlink。
          如果未指定,则仅使用主模块(如果指定了 --module),
          或者使用默认的模块集(如果指定了
          --main-jar)。
          可以多次使用此选项。
  --module-path -p <module path>...
          路径的 ; 分隔列表
          每个路径要么是模块的目录,要么是
          模块化 jar 的路径。
          (每个路径可以是绝对路径,也可以是相对于当前目录的路径。)
          可以多次使用此选项。
  --jlink-options <jlink 选项>
          要传递给 jlink 的选项列表(用空格分隔)
          如果未指定,则默认为 "--strip-native-commands
          --strip-debug --no-man-pages --no-header-files"。
          可以多次使用此选项。
  --runtime-image <directory path>
          将复制到应用程序映像的预定义
          运行时映像的路径
          (绝对路径或相对于当前目录的路径)
          如果未指定 --runtime-image,jpackage 将运行 jlink 以
          使用如下选项创建运行时映像:
          --strip-debug、--no-header-files、--no-man-pages 和
          --strip-native-commands。

用来创建应用程序映像的选项:
  --input -i <directory path>
          包含要打包的文件的输入目录的路径
          (绝对路径或相对于当前目录的路径)
          输入目录中的所有文件将打包到
          应用程序映像中。
  --app-content <additional content>[,<additional content>...]
          要添加到应用程序有效负载中的文件和/或
          目录的逗号分隔路径列表。
          此选项可以多次使用。

用来创建应用程序启动程序的选项:
  --add-launcher <launcher name>=<file path>
          启动程序的名称和包含关键字-值对列表的
          属性文件的路径
          (绝对路径或相对于当前目录的路径)
          可以使用关键字 "module""main-jar""main-class""description""arguments""java-options""app-version""icon""launcher-as-service""win-console""win-shortcut""win-menu""linux-app-category""linux-shortcut"。
          这些选项将添加到原始命令行选项中或者用来覆盖
          原始命令行选项,以构建额外的替代启动程序。
          将从命令行选项构建主应用程序启动程序。
          可以使用此选项构建额外的替代启动程序,
          可以多次使用此选项来构建
          多个额外的启动程序。
  --arguments <main class arguments>
          在没有为启动程序提供命令行参数时,
          要传递到主类的命令行参数
          可以多次使用此选项。
  --java-options <java options>
          要传递到 Java 运行时的选项
          可以多次使用此选项。
  --main-class <class name>
          要执行的应用程序主类的限定名称
          只有在指定了 --main-jar 时才能使用此选项。
  --main-jar <main jar file>
          应用程序的主 JAR;包含主类
          (指定为相对于输入路径的路径)
          可以指定 --module 或 --main-jar 选项,但是不能同时指定
          两者。
  --module -m <module name>[/<main class>]
          应用程序的主模块(以及可选的主类)
          此模块必须位于模块路径中。
          如果指定了此选项,则将在 Java 运行时映像中
          链接主模块。可以指定 --module 或 --main-jar 选项,
          但是不能同时指定这两个选项。

用来创建应用程序启动程序的与平台相关的选项:
  --win-console
          为应用程序创建控制台启动程序,应当为
          需要控制台交互的应用程序指定

用来创建应用程序包的选项:
  --about-url <url>
          应用程序主页的 URL
  --app-image <directory path>
          用来构建可安装程序包的
          预定义应用程序映像的位置
          (绝对路径或相对于当前目录的路径)
  --file-associations <file path>
          包含关键字-值对列表的属性文件的路径
          (绝对路径或相对于当前目录的路径)
          可以使用关键字 "extension""mime-type""icon""description"
          来描述此关联。
          可以多次使用此选项。
  --install-dir <directory path>
          默认安装位置下面的相对子路径
  --license-file <file path>
          许可证文件的路径
          (绝对路径或相对于当前目录的路径)
  --resource-dir <directory path>
          覆盖 jpackage 资源的路径
          可以通过向该目录中添加替代资源来覆盖 jpackage 的
          图标、模板文件和其他资源。
          (绝对路径或相对于当前目录的路径)
  --runtime-image <directory path>
          要安装的预定义运行时映像的路径
          (绝对路径或相对于当前目录的路径)
          在创建运行时程序包时需要使用选项。
  --launcher-as-service
          请求创建安装程序,以将主
          应用程序启动程序注册为后台服务类型应用程序。

用来创建应用程序包的与平台相关的选项:
  --win-dir-chooser
          添加一个对话框以允许用户选择
          产品的安装目录。
  --win-help-url <url>
          用户可以从中获取更多信息或技术支持的 URL
  --win-menu
          请求为此应用程序添加开始菜单快捷方式
  --win-menu-group <menu group name>
          此应用程序所在的开始菜单组
  --win-per-user-install
          请求基于每个用户执行安装
  --win-shortcut
          请求为此应用程序添加桌面快捷方式
  --win-shortcut-prompt
          添加一个对话框以允许用户选择是否将由安装程序
          创建快捷方式。
  --win-update-url <url>
          可用应用程序更新信息的 URL
  --win-upgrade-uuid <id string>
          与此程序包的升级相关联的 UUID

17、JEP 393:外部存储器访问 API(第三次孵化)

JEP 393 (core-libs)

孵化阶段。

功能进化

java版本特性类型JEP特性
Java 14孵化特性JEP 370引入了外部内存访问 API作为孵化特性
Java 15第二次孵化JEP 383优化外部内存访问 API
Java 16孵化特性JEP 389引入了外部链接器 API
Java 16第三次孵化JEP 393功能优化

18、JEP 396:默认强封装 JDK 内部元素

JEP 396 (core-libs)

在 Java 16 之前,许多 JDK 内部的类和成员尽管没有正式成为公共 API,但仍然可以被外部代码所访问。从 Java 16 开始,默认情况下,这些内部 API 被强制封装,阻止了对它们的非法访问,但关键的内部API(如sun.misc.Unsafe)除外。防止开发者依赖于非稳定的、未经官方支持的内部实现,提高了代码的长期稳定性。

启动器选项的默认值--illegal-access 现在是deny而不是permit。这可能造成,那些使用了JDK内部的类、方法或字段的大多数代码将无法运行。临时可以通过指定--illegal-access=permit在JDK16上运行。但是,该选项将在将来的版本中删除 。

Java 16 明确了哪些是公共 API,哪些是 JDK 内部使用的 API,帮助开发者避免依赖于可能在未来版本中改变的内部实现。鼓励开发者使用稳定且官方支持的公共 API,而不是依赖于内部的、可能随时变更的实现。

19、移除的APIs、工具、容器

参考:

Oracle JDK和OpenJDK之间的差异

尽管官方已经声明了让OpenJDK和Oracle JDK二进制文件尽可能接近的目标,但至少对于JDK 16来说,这两者之间仍然存在一些差异。

目前的差异是:

  • Oracle JDK提供了安装程序(msi、rpm、deb等),它们不仅将JDK二进制文件放置在系统中,还包含更新规则,在某些情况下还可以处理一些常见的配置,如设置常见的环境变量(如Windows中的JAVA_HOME)和建立文件关联(如使用JAVA启动.jar文件)。OpenJDK仅作为压缩档案(tar.gz或.zip)提供。
  • Usage Logging仅在Oracle JDK中可用。
  • Oracle JDK要求使用Java加密扩展(JCE(Java Cryptography Extension ))代码签名证书对第三方加密提供程序进行签名。OpenJDK继续允许使用未签名的第三方加密提供程序。
  • java -version命令输出结果不同。Oracle JDK将输出java并包含LTS。Oracle生成的OpenJDK将显示OpenJDK,不包括Oracle特定的LTS标识符。
  • Oracle JDK将在OTN许可证下发布。任何许可证文件都需要指向OTN。OpenJDK将在GPLv2wCP下发布,并将包括GPL许可证。
  • Oracle JDK将在FreeType许可证下分发FreeType,而OpenJDK则将在GPLv2下分发。因此,\legal\java.desktop\freetype.md的内容将有所不同。
  • Oracle JDK有Java cup和steam图标,而OpenJDK有Duke图标。
  • Oracle JDK源代码包括ORACLE PROPRIETARY/CONFIDENTIAL. 使用受许可条款约束的说明(OTN(Oracle Technology Network License Agreement for Oracle Java SE )协议),OpenJDK源代码包含GPL协议。