JDK19 新特性使用详解
JDK 前提 JDK19 于 2022-09-20 发布 GA 版本,本文将会详细介绍 JDK19 新特性的使用。
新特性列表新特性列表如下:
JPE-405 : Record 模式(预览功能)JPE-422 : JDK 移植到 Linux/RISC-VJPE-424 :外部函数和内存 API (预览功能)JPE-425 :虚拟线程,也就是协程(预览功能)JPE-426 :向量 API (第四次孵化)JPE-427 : switch 匹配模式(第三次预览)JPE-428 :结构化并发(孵化功能)新特性使用详解下面就每个新特性介绍其使用方式。
Record 模式使用 Record 模式增强 Java 编程语言以解构 Record 值。可以嵌套 Record 模式和 Type 模式,以实现强大的、声明性的和可组合的数据导航和处理形式。这个描述看起来有点抽象,下面举几个 JEP-405 的例子结合文字理解一下。以 JDK16 扩展的 instanceof 关键字下使用 Type 模式来看:
// JDK16 以前 private static void oldInstanceOf(Object x) {if (x instanceof String) {String s = (String) x;System.out.println(s);}}
// JDK16 或之后启用 instanceof 下的 Type 模式 private static void newInstanceOfTypePattern(Object x) {if (x instanceof String s) {System.out.println(s);}}Type 模式在 JDK17 和 JDK18 扩展到 switch 预览功能中,应用于其 case 标签:
// DEMO-1private static void switchTypePattern(String s) {switch (s) {case null -> System.out.println("NULL");case "Foo", "Bar" -> System.out.println("Foo or Bar");default -> System.out.println("Default");}}
// DEMO-2interface Shape{}class Rectangle implements Shape{}class Triangle implements Shape{public int calculateArea(){return 200;}}
private static void switchTypePatternForShape(Shape shape) {switch (shape) {case null:break;case Rectangle r:System.out.printf("Rectangle[%s]\n", r);break;case Triangle t:if (t.calculateArea() > 100) {System.out.printf("Large triangle[%s]\n", t);}default:System.out.println("Default shape");}}
// DEMO-3 patterns in labelsprivate static void switchTypeForLabels(Object x) {String formatted = switch (x) {case Integer i -> String.format("int => %d", i);case Long l -> String.format("long => %d", l);case Double d -> String.format("double => %f", d);case String s -> String.format("string => %s", s);default -> x.toString();};}本次的 Record 模式预览功能就是基于 record 关键字实现上面的 Type 类型或者 switch 模式。例如:
// DEMO-1record Point(int x,int y){}
private static void printSum(Object o){if (o instanceof Point(int x,int y)){System.out.println(x + y);}}record 类中如果存在泛型参数可以进行类型转换和推导,例如:
// DEMO-2record Holder<T>(T target){}
// 擦除后 private void convert(Holder<Object> holder){if (Objects.nonNull(holder) && holder instanceof Holder<Object>(String target)) {System.out.printf("string => %s\n", target);}}
// 非擦除 private <T> void convert(Holder<T> holder){if (Objects.nonNull(holder) && holder instanceof Holder<T>(String target)) {System.out.printf("string => %s\n", target);}}然后看 record 和 switch 结合使用:
// DEMO-3sealed interface I permits C, D {}final class C implements I {}final class D implements I {}
Second<I,I> second;
private void recordSwitch() {second = new Second<>(new D(), new C());// second = new Second<>(new C(), new D());switch (second) {case Second<I, I>(C c,D d) -> System.out.printf("c => %s,d => %s", c, d);case Second<I, I>(D d,C c) -> System.out.printf("d => %s,c => %s", d, c);default -> System.out.println("default");}}这种模式比较复杂,因为涉及到 record 类、 switch 模式、泛型参数并且参数类型是接口, case 子句处理的时候必须覆盖该泛型参数接口的所有子类型
不得不说,JDK 引入的语法糖越来越复杂,功能看起来是强大的,但是编码的可读性在未适应期有所下降
Linux/RISC-V 移植通过 Linux/RISC-V 移植, Java 将获得对硬件指令集的支持,该指令集已被广泛的语言工具链支持。 RISC-V 是一种包含矢量指令的通用 64 位 ISA ,目前该端口支持以下的 HotSpot VM 选项:
模板解释器客户端 JIT 编译器服务端 JIT 编译器包括 ZGC 和 Shenandoah 在内的主流垃圾收集器该移植基本已经完成, JEP 的重点是将该端口集成到 JDK 的主仓库中。
外部函数和内存 API 外部函数和内存 API 的主要功能是引入一组 API , Java 程序可以通过该组 API 与 Java 运行时之外的代码和数据进行交互。有以下目标:
易用性:通过卓越的纯 Java 开发模型代替 JNI 高性能:提供能与当前 JNI 或者 Unsafe 相当甚至更优的性能通用性:提供支持不同种类的外部内存(如本地内存、持久化内存和托管堆内存)的 API ,并随着时间推移支持其他操作系统甚至其他语言编写的外部函数安全性:允许程序对外部内存执行不安全的操作,但默认警告用户此类操作核心的 API 和功能如下:
分配外部内存: MemorySegment 、 MemoryAddress 和 SegmentAllocator 操作和访问结构化的外部内存: MemoryLayout 和 VarHandle 控制外部内存: MemorySession 调用外部函数: Linker 、 FunctionDescriptor 和 SymbolLookup 这些 API 统称为 FFM API ,位于 java.base 模块的 java.lang.foreign 包中。由于 API 比较多并且不算简单,这里只举一个简单的例子:
public class AllocMemoryMain {
}
// 某次执行输出结果 index => 0, x = 0, y = 0index => 1, x = 79, y = 16index => 2, x = 164, y = 134index => 3, x = 150, y = 60index => 4, x = 152, y = 232index => 5, x = 495, y = 240index => 6, x = 54, y = 162index => 7, x = 406, y = 644index => 8, x = 464, y = 144index => 9, x = 153, y = 342Point[5].x = 495Point[6].y = 162FFM API 是一组极度强大的 API ,有了它可以灵活地安全地使用外部内存和外部(跨语言)函数。
虚拟线程虚拟线程,就是轻量级线程,也就是俗称的协程,虚拟线程的资源分配和调度由 VM 实现,与平台线程( platform thread )有很大的不同。从目前的源代码来看,虚拟线程的状态管理、任务提交、休眠和唤醒等也是完全由 VM 实现。可以通过下面的方式创建虚拟线程:
// 方式一:直接启动虚拟线程,因为默认参数原因这样启动的虚拟线程名称为空字符串 Thread.startVirtualThread(() -> {Thread thread = Thread.currentThread();System.out.printf("线程名称:%s,是否虚拟线程:%s\n", thread.getName(), thread.isVirtual());});
// 方式二:Builder 模式构建 Thread vt = Thread.ofVirtual().allowSetThreadLocals(false).name("VirtualWorker-", 0).inheritInheritableThreadLocals(false).unstarted(() -> {Thread thread = Thread.currentThread();System.out.printf("线程名称:%s,是否虚拟线程:%s\n", thread.getName(), thread.isVirtual());});vt.start();
// 方式三:Factory 模式构建 ThreadFactory factory = Thread.ofVirtual().allowSetThreadLocals(false).name("VirtualFactoryWorker-", 0).inheritInheritableThreadLocals(false).factory();Thread virtualWorker = factory.newThread(() -> {Thread thread = Thread.currentThread();System.out.printf("线程名称:%s,是否虚拟线程:%s\n", thread.getName(), thread.isVirtual());});virtualWorker.start();// 可以构建"虚拟线程池"ExecutorService executorService = Executors.newThreadPerTaskExecutor(factory);由于虚拟线程的功能还处于预览阶段,创建协程的时候无法自定义执行器(准确来说是 运载线程 ),目前所有虚拟线程都是交由一个内置的全局 ForkJoinPool 实例执行,实现方式上和 JDK8 中新增的 并行流 比较接近。另外,目前来看虚拟线程和原来的 JUC 类库是 亲和 的,可以把虚拟线程替换原来 JUC 类库中的 Thread 实例来尝试使用(在生产应用建议等该功能正式发布)
向量 API 向量 API 目前是第四次孵化,功能是表达向量计算,在运行时编译为 CPU 架构上的最佳向量指令,从而实现优于等效标量计算的性能。目前相关 API 都在 jdk.incubator.vector 包下,使用的例子如下:
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256;
private static 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);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);}}
public static void main(String[] args) {float[] a = new float[]{1.0f, 3.0f, 2.0f};float[] b = {1.0f, -1.0f, 5.0f};float[] c = {1.0f, 6.0f, 1.0f};vectorComputation(a, b, c);System.out.println(Arrays.toString(c));}Vector 有很多特化子类,可以通过不同的 VectorSpecies 进行定义。
switch 匹配模式 switch 匹配模式第三次预览,主要是对匹配模式进行了扩展。主要有几点改进:
增强类型校验, case 子句支持多种类型 record Point(int i, int j) {}enum Color { RED, GREEN, BLUE; }
private void multiTypeCase(Object o) {switch (o) {case null -> System.out.println("null");case String s -> System.out.println("String");case Color c -> System.out.println("Color: " + c.toString());case Point p -> System.out.println("Record class: " + p.toString());case int[] ia -> System.out.println("Array of ints of length" + ia.length);default -> System.out.println("Something else");}}增强表达式和语句的表现力和适用性,可以实现 selector 模式 private int selector(Object o) {return switch (o) {case String s -> s.length();case Integer i -> i;default -> 0;};}扩展模式变量声明范围 private void switchScope(Object o) {switch (o) {case Character cwhen c.charValue() == 7:System.out.println("Seven!");break;default:break;}}优化 null 处理 private void switchNull(Object o) {switch (o) {case null -> System.out.println("null!");case String s -> System.out.println("String");default -> System.out.println("Something else");}}结构化并发结构化并发功能在孵化阶段,该功能旨在通过结构化并发库来简化多线程编程。结构化并发提供的特性将在不同线程中运行的多个任务视为一个工作单元,以简化错误处理和取消,提高了可靠性和可观测性。
record User(String name, Long id){}record Order(String orderNo, Long id){}record Response(User user, Order order){}
private User findUser(){throw new UnsupportedOperationException("findUser");}
private Order fetchOrder(){throw new UnsupportedOperationException("fetchOrder");}
private Response handle() throws ExecutionException, InterruptedException {try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {Future<User> user = scope.fork(() -> findUser());Future<Order> order = scope.fork(() -> fetchOrder());scope.join(); // Join both forksscope.throwIfFailed(); // ... and propagate errors// Here, both forks have succeeded, so compose their resultsreturn new Response(user.resultNow(), order.resultNow());}}
评论