探索Java 16提供的功能。
时光飞逝!即使在这样的非凡时期,似乎也很难相信已经过去了六个月,现在我们有了JDK的新版本。像往常一样,我将总结所有新功能,并对它们对Java应用程序开发的潜在影响进行一些评论。
并非所有版本都是相同的,并且取决于功能开发阶段的对齐方式。一些将比其他具有更多的功能。 JDK 16包含了许多新内容,尽管其中一些是孵化器模块的延续或完成,或者是较早版本的预览功能。我认为这是更快发布节奏的最大好处之一。提供新功能而不使其成为标准的一部分,收集开发人员的反馈,并可能进行更改,这些都为JDK开发提供了改进的过程。
我将采用通常的方法将帖子分为语言更改,库添加,与JVM相关的更新,以及其他所有内容。
Java语言
JDK 16中没有新的语法构造,但是仍然存在一些重大变化。
JEP 394:instanceof的模式匹配
这是在JDK 14中作为预览功能引入的,并在JDK 15中以预览模式继续进行。此功能已在JDK 16中完成。它包含在Java SE 16语言规范中,不需要–enable-preview 命令行标志即可进行编译和运行。
该功能在JDK 15中的工作方式有两个小变化。首先是模式变量不再隐式最终。这是非常合乎逻辑的更改,因为未将局部变量视为隐式最终变量。
第二个变化是现在是表达式模式instanceof的编译时错误,该表达式实例将类型S的表达式与类型T的模式进行比较,其中S是T的子类型。这是一个例子:
var a = new ArrayList
if (a instanceof List l)
System.out.println(l.size());
这将导致编译器错误:
Error: pattern type java.util.List is a subtype of expression type
java.util.ArrayList
这似乎是合乎逻辑的,因为if语句是多余的,因为该谓词始终会评估为true。
JEP 395:记录
JDK 14中引入的另一个预览功能现已在JDK 16中完成。对我来说,这是对Java语言的有益补充,因为我们终于有了一种处理元组的简单方法。
JDK 15实现对Records的唯一更改是放宽了长期的限制,即内部类不能声明显式或隐式静态的成员。这对于简化某些流操作非常有用。通常,希望每个元素的流都通过一个以上的对象。现在,可以定义本地记录,以减少原本会导致的类型污染。
JEP 397:密封类
此功能是在JDK 15中作为预览功能引入的,并在JDK 16中继续作为预览功能。
有三处更改:
添加新的语言语法通常需要新的关键字。将这些作为保留字添加到语言中(例如JDK
1.4中的assert)会严重影响向后兼容性,因为这些字不再可用作标识符。我们已经有了限制类型和限制关键字(例如var和seal)的概念来解决此问题。该JEP引入了上下文关键字的概念。具体来说,对于JDK
16,密封,非密封和允许是上下文关键字。
与匿名类和lambda表达式一样,在确定密封类或接口的隐式声明的允许子类时,局部类可能不是密封类的子类。
JLS中变窄的参考转换定义已扩展为在密封的层次结构中导航,以确定在编译时无法进行哪些转换。
类库
通过各个JEP定义了四个新库:
JEP 338:矢量API
不要与collections API中不推荐使用的Vector类混淆,这是一个使开发人员能够更好地使用基础CPU向量功能的库。所有现代处理器都具有单指令多数据(SIMD)功能。阵列的多个元素可以加载到非常宽的寄存器中,例如特定Intel处理器上的512位AVX-512。例如,可以在单个机器指令周期内执行将每个值加10的单个操作,从而显着提高数字密集型代码的性能。问题在于,编译器需要识别可以转换为向量运算的代码,这可能非常困难,尤其是在涉及条件运算的情况下。
向量API为开发人员提供了一种机制,可向编译器明确表明应使用向量操作。但是,这确实使代码更加复杂。首先,有必要获得一种载体。无论是要使用的寄存器大小还是要加载的数据类型(例如浮点数),这都是所需的向量形式。然后创建特定于类型的向量,并根据需要将其装入值。最后一部分是使用适当的数学函数来操纵向量。
所有这些都可以在从JEP提起的示例中看到。
static final VectorSpecies
void vectorComputation(float[] a, float[] b, float[] c) {
for (int i = 0; i < a.length; i += SPECIES.length()) {
var m = SPECIES.indexInRange(i, a.length);
// FloatVector va, vb, vc;
var va = FloatVector.fromArray(SPECIES, a, i, m);
var vb = FloatVector.fromArray(SPECIES, b, i, m);
var vc = va.mul(va)
.add(vb.mul(vb))
.neg();
vc.intoArray(c, i, m);
}
}
它是作为孵化器模块提供的,因此在包含在Java SE标准中之前可能会有所更改。我希望开发人员将意识到使用缩写是不必要的(或理想的)。我宁愿看到mulple(),而不是mul(),依此类推。
JEP 380:UNIX域套接字通道
UNIX域套接字已经存在了很长时间,可追溯到1983年的BSD变体。但是,直到2018年,它们才在Windows平台中引入。由于Java以跨平台为荣,因此拥有一个无法在任何地方运行的库没有多大意义。
现在,我们有了UNIX域套接字API,该API提供了一种在单个主机上处理进程间通信的简便方法。这些套接字与使用TCP / IP环回非常相似,只不过它们是通过文件系统路径名而不是IP地址和端口号来寻址的。UNIX域套接字的优点是它们比回送更为有效和安全。
JEP 393:外部内存访问API
该库在JDK 14中作为孵化器模块引入,在JDK 16中是其第三次迭代。
该API允许Java程序安全有效地访问Java堆外部的外部内存。它与Project Panama有关,该项目旨在取代Java本机接口,以使Java应用程序与本机库进行交互。
JDK 15孵化器版本有四个更改:
MemorySegment和MemoryAddress接口之间的角色更加清晰地分离。
一个新的接口MemoryAccess,提供了常见的静态内存访问器,以在简单情况下将对VarHandle API的需求降至最低。
支持共享细分。
向清理器注册段的能力
JEP 389:外部链接器API
作为孵化器模块,它引入了一个API,该API提供了对本地代码的静态类型的纯Java访问。这也是Project Panama的一部分,并且与Foreign Memory Access API结合使用,使绑定到本地库变得更加简单。最初的实现侧重于用C编写的库。
首先必须在库中找到适当的符号,才能与外来功能进行交互。LibraryLookup接口用于完成此操作,可以通过java.library.path上的库或使用显式文件路径从JVM中加载的符号中找到符号。
找到了要使用的外部函数之后,Clinker接口提供了两种在Java代码和函数之间进行交互的方法:
downcallHandler方法返回一个MethodHandle,该方法可用于以与Java方法相同的方式调用外部函数。
upCallStub方法返回一个MemorySegment(来自外部存储器访问API)。当外部函数需要回调Java方法(通常作为回调方法)时使用它。
当您查看使用外部链接器API的示例时,代码似乎非常复杂。巴拿马项目包含jextract工具,该工具尚未包含在JDK中。通过删除所有样板代码并允许简单的静态导入来访问外部函数,这大大简化了事情。希望这将使它成为JDK 17。
其他API变更
更新的Java SE规范中包括58个新的API元素,包括上述的UNIX域套接字。以下是我认为对开发人员有趣的其他一些内容。
java.lang
在Class中,allowedSubClasses()已替换为getPermittedSubClasses()。
IndexOutOfBoundsException现在具有一个新的构造函数,该构造函数需要很长时间才能处理索引所引用的索引大于int的情况。
java.nio
ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer和ShortBuffer都有一个新的put()方法。这些方法从第二个缓冲区的给定偏移量开始复制第二个缓冲区的定义长度部分,到该方法被调用的缓冲区中的指定索引。该描述似乎很复杂,但是它是循环的简单替代:
for (int i = offset, j = index; i < offset + length; i++, j++)
destinationBuffer.put(j, sourceBuffer.get(i));
java.time.format
DateTimeFormatterBuilder类具有一个新方法appendDayPeriodText()。这提供了一种将一天中的时间格式化为“上午”,“下午”等的方式,而不仅仅是AM或PM
javax.swing
很高兴看到Swing仍在获取更新,即使它们很小。
JPasswordField有一个新方法setText()。如果您想用已经记住的密码字段预先填充密码字段,这将很有用(这是我处理网站登录凭据的唯一方法)。
AccessibleJSlider有一个新的侦听器stateChanged()
java.util.logging
LogRecord有两个新方法,即getLongThreadID()和setLongThreadID()。这些是现在不建议使用的get / setThreadID方法的替代方法,该方法限于整数值。
JDK 16中的流API进行了一些激动人心的更改。
首先是在Stream接口上包含作为默认方法提供的新终端操作。这是toList(),它将流中的元素累积到不可修改的列表中。使列表不可修改已促使在Reddit之类的地方进行了一些讨论,但这似乎是合乎逻辑的选择。
第二个变化是引入mapMulti()作为中间操作,再次作为流上的一组默认方法提供(这包括针对double,int和long的特定于类型的方法)。当您第一次阅读此操作的说明时,它看起来与flatMap相同。它返回一个流,该流包括用多个元素(特别是零个或多个元素)替换此流的每个元素的结果。与flatMap()有效地连接输入流中每个元素生成的流不同,mapMulti()应用映射可以导致生成多个元素。如果要使用mapMulti()映射到零个或一个元素,则它与过滤操作相同。如果使用它仅映射到一个元素,则它将与map()相同。好处是当您要为每个输入元素创建多个元素时。一个简单的示例如下所示:
`wordList.stream()
.mapMulti((str, consumer) -> {
for (int i = 0; I < str.length(); i++)
consumer.accept(str.length());
})
.forEach(System.out::print);`
对于单词流,结果将是每个单词的长度打印该次数。这里重要的是,可以根据需要多次调用使用者上的accept()方法。
JDK 16(javax.tools.ToolProvider类的构造函数)中仅删除了一个先前不推荐使用的API。
JVM相关功能
JEP 376将ZGC线程堆栈处理从安全点移至并发阶段,即使在大型堆上,也可以大大减少GC安全点内部的暂停。ZGC的初始阶段(与许多收集器一样)建立了应用程序可访问的对象的根集合。为此,需要遍历堆栈以查找对象引用。过去,这要求所有线程都达到全局安全点,在该点上,所有线程都可以安全地暂停并完成工作。在ZGC中,这不再是必需的,可以与应用程序线程同时完成。有趣的是,此设计方式并未将其绑定到ZGC,因此Shenandoah项目也选择使用此功能。
元空间是永久代的替代,永久代曾经包含在堆中。 JEP 387引入了弹性元空间,其中使用的内存可以更迅速地返回操作系统。该代码也得到了简化,从而减少了维护开销。
其他JDK功能
多年来,OpenJDK项目基础结构最重大的变化是从Mercurial到Git的OpenJDK源代码管理系统(JEP 357)迁移,并将所有代码托管在Github(JEP 369)上。Git / Github在开发人员中非常受欢迎,因此这是一个明智的决定,它将帮助吸引更多的人参与该项目。
与JVM源代码也特别相关的是允许使用C ++ 14功能(JEP 347)的举动。似乎使用更新的语言功能确实步履蹒跚,因为我们现在才包括使用已有七年历史的功能。想象一下这会给JVM开发人员带来的兴奋,他们现在可以使用二进制文字了!
JDK有两个新的端口,从JDK 16开始:
高山Linux(JEP 386):这主要针对希望部署基于Java的微服务的用户。Alpine
Linux使用C库的Musl实现来提供最少的Linux环境。结合使用jlink生成的JDK,Docker映像可小至38Mb。诚然,这仅包括java.base模块,但这是一个很好的起点。
Windows / AArch64(JEP 388):Arm
64处理器在服务器端和客户端都变得越来越流行。此端口将有助于进一步提高Java的流行度。从一开始就参与Java,我必须说,很高兴看到Microsoft为OpenJDK项目做出了贡献!
Valhalla项目希望将值类型带入Java,在交付Java之前,需要对Java进行一些细微的更改。现在将现有的原始包装器类(例如Integer)指定为基于值的类,并且不建议使用构造函数将其删除。通常,无论如何都不会使用它们,但是如果您使用了它们,则会发出适当的编译器警告。如果尝试在这些类的实例上进行同步,还将生成警告消息。JEP 390中对此进行了详细说明。
JDK 9引入了Java Platform Module System,其中一个必然的部分是JEP 260,它封装了JDK的大多数内部API。因为这有可能破坏许多现有代码,所以包含了一个命令行标志:–illegal-access,它可以具有以下四个值之一:allow,warn,debug和deny。从JDK 9开始,默认值是允许的,从而允许使用关键内部API的代码继续按预期运行。在JDK 16中,感谢JEP 396,默认情况下,JDK内部API严格封装(将标志值更改为拒绝)。JEP声明这将不会封装尚不存在标准替代品的API。这意味着sun.misc.Unsafe将保持可用。现在,一些在JDK 15中生成警告的反射代码将引发异常并终止,因此请小心!
尽管尚未得到确认,但在JDK 17中似乎不再可能放松封装,从而有效地删除了–非法访问标志。
包装工具
在JDK 11之前,Oracle将JavaFX包含在其JDK构建中。这提供了一个不错的工具,可以为Java应用程序生成可安装的,特定于平台的软件包。删除此功能后,开发人员表示希望恢复此功能。它作为孵化器模块包含在JDK 14中(尽管不是确切的模块,但孵化器也涵盖了工具)。现在,它已作为完整功能包含在JDK 16中。与JDK 14相比,唯一的变化是将–bind-services命令行标志替换为更通用的–jlink-options标志。
JDK中删除了两个实验功能,特别是Graal JIT编译器和提前工具jaot。据推测,这是因为Oracle正在投资并推广包括此功能的GraalVM。
结论
JDK 16继续了Java平台的快速变更步伐。我们真的开始看到预览功能和孵化器模块的功能和优势。
使用JDK 17,我们将看到下一个长期支持版本。尽管这不是OpenJDK的概念,但它是所有OpenJDK二进制发行版(包括Azul的Zulu)提供程序所提供的。因此,您绝对应该使用JDK 16测试您的应用程序,以查看这些更改是否有任何影响。
您准备好使用现代Java了吗?
(注意:我计算了67个新功能和API,其中包括17个JEP和50个新Java SE API元素。UNIX域套接字占8个API元素)。
参考: 《2020最新Java基础精讲视频教程和学习路线!》
链接:https://blog.csdn.net/weixin_...