一、Java 18
Java 18于2022年3月22日发布,共有9个特性,本次精选5个特性:
更多内容请读者自行阅读:OpenJDK Java 18官方文档
1.1 JEP 400:Java字符集默认为UTF-8
在Java 18之前,用于读和写的API,比如FileReaader、FileWriter,都有charset参数用于指定字符集,如果没传charset的话,字符集则由JDK启动时的运行环境选择默认的字符集,这就会导致一个问题:在没有指定字符集的情况下,在Mac上运行正常的一段打印文字的Java程序,转到windows上就会出现乱码。
所以在Java 18,JDK将UTF-8设置为默认字符集,在没有指定charset的情况下,不再由JDK运行环境确定。
1.2 JEP 416:反射优化
Java 18改进了反射功能,在 java.lang.invoke 方法句柄之上重新实现 java.lang.reflect.Method、Constructor 和 Field,使之性能更好。
下面是OpenJDK官方给出的新老实现反射性能基准测试结果:
1.3 JEP 417:向量API(第三轮孵化)
Java 18也对向量API进行了微调,有两点改动:
支持ARM标量向量扩展(SVE)平台。
提高在支持硬件掩码的架构上接受掩码的矢量操作的性能。
这个在Java 16时介绍过,这里就不再赘述了。
1.4 JEP 419:外部函数和内存API(第二轮孵化)
外部函数和内存API是由JEP 412提出的,并在2021年中期作为孵化API于Java 17。它结合了两个早期的孵化 API:Foreign-Memory Access API 和 Foreign Linker API。
此 JEP 提议根据反馈进行改进,并在 Java 18 中重新孵化 API。
ps:第一个预览版本在Java 19提出,所以将会在Java 19再详细介绍。
1.5 JEP 420:Switch模式匹配(二次预览)
二、Java 19
Java 19发布于2022年9月29日,共有7个特性,本次精选6个:
更多内容请读者自行阅读:OpenJDK Java 19官方文档
2.1 JEP 405:Record 模式(一次预览)
我们回忆下,在 JDK 16 中,JEP 394 扩展了 instanceof 运算符(不必强转直接用):
if(obj instanceof String str){
System.out.println("字符串:"+str);
}
然后在Java 17、Java 18还扩展了和Switch的结合使用。
还记得Java 14引入的Records类吗,在Java 19用instanceof进一步增强了Records类的语法:
public record Point(int x, int y) {}
void printSum(Object o){
if(o instanceof Point(int x,int y)){
System.out.println(x+y);
}
}
2.2 JEP 424:外部函数和内存API(一次预览)
外部函数和内存(FFM)API结合了两个早期的孵化API:外部内存访问API(JEPs 370、383和393)和外部链接器API(JEP 389)。
通过该 API,Java 程序可以与 Java 运行时之外的代码和数据进行互操作。通过高效调用外部函数(即 JVM 外部的代码)和安全访问外部内存(即不受 JVM 管理的内存),API 使 Java 程序能够调用本机库并处理本机数据,而不会出现 JNI 的脆弱性和危险性。
前面也提到过,在没有外部函数和内存API之前,开发人员如果想要操作堆外内存,通常的做法就是使用ByteBuffer、Unsafe或JNI等方式,但无论哪种方式都无法有效解决安全性和高效性等2个问题,并且堆外内存的释放也是一个头疼的问题。
为了解决这些痛点,The Foreign Function & Memory API (FFM API)定义了类和接口:
分配堆外内存:MemorySegment
、
MemoryAddress和
SegmentAllocator。操作和访问结构化的堆外内存:MemoryLayout
,
VarHandle。控制堆外内存的分配和释放:MemorySession。
调用外部函数:Linker
,
FunctionDescriptor, and
SymbolLookup。
下面是FFM API的使用示例(来自官方文档),这段代码获取了C库函数的radixsort方法句柄,然后使用它对Java数组中的四个字符串进行排序:
public static void main(String[] args) {
// 1. 在C库路径上查找外部函数
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle radixSort = linker.downcallHandle(
stdlib.lookup("radixsort"), ...);
// 2. 分配堆上内存以存储四个字符串
String[] javaStrings = { "mouse", "cat", "dog", "car" };
// 3. 分配堆外内存以存储四个指针
SegmentAllocator allocator = SegmentAllocator.implicitAllocator();
MemorySegment offHeap = allocator.allocateArray(ValueLayout.ADDRESS, javaStrings.length);
// 4. 将字符串从堆上复制到堆外
for (int i = 0; i < javaStrings.length; i++) {
// 在堆外分配一个字符串,然后存储指向它的指针
MemorySegment cString = allocator.allocateUtf8String(javaStrings[i]);
offHeap.setAtIndex(ValueLayout.ADDRESS, i, cString);
}
// 5. 通过调用外部函数对堆外数据进行排序
radixSort.invoke(offHeap, javaStrings.length, MemoryAddress.NULL, '\0');
// 6. 将(重新排序的)字符串从堆外复制到堆上
for (int i = 0; i < javaStrings.length; i++) {
MemoryAddress cStringPtr = offHeap.getAtIndex(ValueLayout.ADDRESS, i);
javaStrings[i] = cStringPtr.getUtf8String(0);
}
assert Arrays.equals(javaStrings, new String[] {"car", "cat", "dog", "mouse"}); // true
}
2.3 JEP 425:虚拟线程(一次预览)
这个参考:Java 21的虚拟线程是怎么回事
2.4 JEP 426:向量API(第四轮孵化)
这个在Java 16时介绍过,这里就不再赘述了。
2.5 JEP 427:Switch的模式匹配(第三次预览)
在Java 19允许case null的选项,这意味着不用在switch之前判null了:
void caseNull(Object o){
//不用再提前校验
// if(o == null){
// System.out.println("o is null");
// }
switch (o){
case null -> System.out.println("o is null");
case String s-> System.out.println("String:"+s);
default -> System.out.println("Something else");
}
}
2.6 JEP 428:结构化并发(第一轮孵化)
Java 19引入了结构化并发,一种多线程编程方式,目的是为了通过结构化并发API来简化多线程编程,目前处于孵化阶段。
结构化并发 API 的主要类是 StructuredTaskScope。这个类允许开发者将一个任务组织成一组并发子任务,并将它们作为一个整体进行协调。子任务在各自的线程中执行,通过分别分叉(fork)它们,然后作为一个整体进行合并(join),并且可能作为一个整体进行取消。子任务的成功结果或异常由父任务进行聚合和处理。StructuredTaskScope 将子任务或分叉的生命周期限制在一个明确的词法范围内,在这个范围内,任务与其子任务的所有交互——包括分叉、合并、取消、处理错误和结果组合——都发生在此范围内。
StructuredTaskScope一般工作流程如下:
创建一个作用域(scope)。创建作用域的线程是它的所有者。
在作用域中分叉(fork)并发子任务。
作用域中的任何分叉或作用域的所有者都可以调用作用域的 shutdown() 方法来请求取消所有剩余的子任务。
作用域的所有者作为一个整体加入作用域,即所有分叉。所有者可以调用作用域的
join()
方法,该方法会阻塞直到所有分叉要么完成(成功或失败),要么通过 shutdown() 被取消。或者,所有者可以调用作用域的 joinUntil(java.time.Instant) 方法,该方法接受一个截止时间。加入后,处理任何分叉中的错误并处理它们的结果。
关闭作用域,通常通过 try-with-resources 隐式完成。这会关闭作用域并等待任何滞后的分叉完成。
这是官方给的一个示例:
Response handle() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
//使用fork方法派生线程来执行任务
Future<String> user = scope.fork(() -> findUser());
Future<Integer> order = scope.fork(() -> fetchOrder());
scope.join(); // 将两个fork合并
scope.throwIfFailed(); // ...出现异常(抛异常)
// 代表这两个fork都成功了,组合它们的结果。
return new Response(user.resultNow(), order.resultNow());
}
}
END:更多新特性的介绍,推荐移步至👉 Java新特性学习导航(8~23 持续更新)👈