文章目录
序言
新特性概览
新特性详解及代码实战
JEP 405 - Record模式
JEP 427 - switch模式匹配
JEP 422 - Linux/RISC-V移植
JEP 424 - 外部函数和内存API
JEP 426 - 向量API
JEP 425 - 虚拟线程
JEP 428 - 结构化并发
小结
参考文档
2022年9月20日,Java19发布了!这篇文章通过实例来体验一下Java19的新特性
这是自2018年Java10发布以来,6个月一个版本的第10个发行版。Java SE产品经理,稳住!
Java 19不是LTS,按照Oracle的计划,下一个LTS将于2023年9月发布,也就是Java 21
Java 19共有7个新特性,4个预览版特性 + 2个处于孵化阶段特性 + 1个特性
JEP编号 | 特性英文名 | 特性中文描述 | 关联项目 |
---|---|---|---|
405 | Record Patterns (Preview) | Record模式 | Amber |
422 | Linux/RISC-V Port | Linux/RISC-V移植 | - |
424 | Foreign Function & Memory API (Preview) | 外部函数和内存API | Panama |
425 | Virtual Threads (Preview) | 虚拟线程 | Loom |
426 | Vector API (Fourth Incubator) | 向量API | Panama |
427 | Pattern Matching for switch (Third Preview) | switch模式匹配 | Amber |
428 | Structured Concurrency (Incubator) | 结构化并发 | Loom |
实战环境信息
操作系统:macOS Monterey 12.6 (M1 Chip)
IDEA版本:Community 2022.2.3
下载IDEA社区版最新版2022.2.3,下载JDK19(PS:一台电脑上可以同时安装专业版和社区版)
在IDEA中新建一个工程jdk-feature,选择已经下载好的JDK19,Languate Level选择19预览版
在运行时,IDEA会自动加上--enable-preview参数 ,启用预览功能
产品经理如是说
该JEP扩展了模式匹配以表示更复杂,组合更灵活的数据访问方式,极大提高了开发效率。使用记录模式增强Java编程语言以便于解构记录值,这可以嵌套记录模式和类型模式,实现强大的、声明式的和可组合的数据导航和处理形式
简单来说就是提供了更多的语法糖,接下来看看这个到底甜不甜
JDK 16扩展了instanceof关键字,引入Type模式,在if语句块内使用变量时,不用强制类型转换了
public static void testInstanceOf(Object o) {
// JDK 16以前
if (o instanceof String) {
String s = (String)o;
... 使用 s ...
}
// JDK 16+
if (o instanceof String s) {
... 使用 s ...
}
}
官网给出的一个Record模式示例
public class Jep405Demo {
record Point(int x, int y) {}
static void printSum(Object o) {
if (o instanceof Point(int x, int y)) {
System.out.println(x + y);
}
}
public static void main(String[] args) {
printSum(new Point(3, 6));
}
}
这段代码中有一个record关键字,表示Point是一个java.lang.Record类,Records是JDK 16发布的新特性,record Point(int x, int y) {}在编译后,相当于下边的代码(有点Lombok的味道了)
class Point {
// final类型的属性
private final int x;
private final int y;
// 包含全部属性的构造方法
Point(int x, int y) {
this.x = x;
this.y = y;
}
// getter
int x() { return x; }
int y() { return y; }
public boolean equals(Object o) {
if (!(o instanceof Point)) return false;
Point other = (Point) o;
return other.x == x && other.y == y;
}
public int hashCode() {
return Objects.hash(x, y);
}
public String toString() {
return String.format("Point[x=%d, y=%d]", x, y);
}
}
Record模式常和switch结合使用,接下来介绍JEP 427,JEP 405和JEP 427都是Amber项目下的特性
这是switch模式匹配的第三次预览,对模式匹配进行扩展,主要是以下4点
1.增强类型检查,case表达式支持多种类型
record Point(int i, int j) {}
enum Color { RED, GREEN, BLUE; }
static void typeTester(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");
}
}
注意:对于有父子关系的多个子句, 如果父类型在子类型之前,父类型子句会优先匹配,子类型子句将不可达,会抛出编译期错误。应该调换一下顺序
// 正确写法
static void first(Object o) {
switch (o) {
case String s ->
System.out.println("A string: " + s);
case CharSequence cs ->
System.out.println("A sequence of length " + cs.length());
default -> {
break;
}
}
}
// 错误写法,编译错误
static void error(Object o) {
switch (o) {
case CharSequence cs ->
System.out.println("A sequence of length " + cs.length());
case String s -> // Error - pattern is dominated by previous pattern
System.out.println("A string: " + s);
default -> {
break;
}
}
}
2. switch表达式和语句分支全覆盖检测
static int coverage(Object o) {
return switch (o) { // Error - still not exhaustive
case String s -> s.length();
case Integer i -> i;
};
}
这段代码的分支没有全覆盖。如果传入Long类型,switch就找不到对应的匹配,因此会报编译错误,IDEA的提示为:'switch' expression does not cover all possible input values
增加default语句后可以消除错误
static int coverage(Object o) {
return switch (o) {
case String s -> s.length();
case Integer i -> i;
default -> 0;
};
}
编译器可以自动检测JDK 17发布的特性sealed类,判断是否全覆盖。来看一下这段代码
sealed interface S permits A, B, C {}
final class A implements S {}
final class B implements S {}
record C(int i) implements S {} // Implicitly final
static int testSealedExhaustive(S s) {
return switch (s) {
case A a -> 1;
case B b -> 2;
case C c -> 3;
};
}
在这段代码中,sealed关键字和permits声明了接口S只能被A, B, C三个final类实现,因此3个case表达式已经可以保证全覆盖,就不需要default语句了
3.扩展了模式变量声明范围
以下3种情况都属于模式变量的可用范围
// 第1条规则
static void test(Object o) {
switch (o) {
case Character c
when c.charValue() == 7:
System.out.println("Ding!");
break;
default: break;
}
}
// 第2条规则
static void test(Object o) {
switch (o) {
case Character c -> {
if (c.charValue() == 7) {
System.out.println("Ding!");
}
System.out.println("Character");
}
case Integer i ->
throw new IllegalStateException("Invalid Integer argument: "
+ i.intValue());
default -> {}
}
}
// 第3条规则,编译期错误
// 如果允许这种情况,假设o是Character类型,执行完第1个语句块后,继续执行第2个语句块时,i未初始化
static void test(Object o) {
switch (o) {
case Character c:
if (c.charValue() == 7) {
System.out.print("Ding ");
}
if (c.charValue() == 9) {
System.out.print("Tab ");
}
System.out.println("character");
case Integer i:
// Compile-time error
System.out.println("An integer " + i);
default: break;
}
}
4.优化null处理,可以声明一个null case
在没有这个优化之前,一般要这样处理。否则会抛出NullPointerException
static void test(Object o) {
if (null == o) {
return;
}
switch (o) {
case String s -> System.out.println("String: " + s);
case Integer i -> System.out.println("Integer");
default -> System.out.println("default");
}
}
现在只需要增加一个null case声明
// 显示声明null case
static void test(Object o) {
switch (o) {
case null -> System.out.println("null!");
case String s -> System.out.println("String");
default -> System.out.println("Something else");
}
}
支持这个语法后,结合JDK 16提供的箭头表达式,可以支这样特的特殊表达式
Object o = ...
switch (o) {
case null, String s -> System.out.println("String, including null");
...
}
一个switch案例
记得刚开始学Java时,有一个题目是用switch语法获取一年的各个月份有多少天,写法很繁琐(当时还不知道用Calendar提供的方法calendar.getActualMaximum(Calendar.DAY_OF_MONTH))。但现在就方便多了
public class Jep427Demo {
static boolean isLeapYear(int year) {
return (year % 400 == 0) || (year % 4 == 0 && year % 100 != 0);
}
static int getDaysOfMonth(int year, int month) {
return switch(month) {
case 2 -> isLeapYear(year) ? 29 : 28;
case 1, 3, 5, 7, 8, 10, 12 -> 31;
case 4, 6, 9, 11 -> 30;
default -> 0;
};
}
public static void main(String[] args) {
System.out.println(getDaysOfMonth(2020, 3));
}
}
通过Linux/RISC-V移植,Java将获得对硬件指令集的支持,该指令集已被广泛的语言工具链支持。RISC-V是一种包含矢量指令的通用64位ISA,移植后将支持以下的HotSpot子系统
这个JEP的关注点是通过移植集成到JDK的主仓库中,而不是移植工作量,该移植基本完成
引入的一组API,让Java程序与Java运行时之外的代码(JVM之外的代码)和数据(不受JVM管理的内存)进行交互,而不通过JNI操作
外部内存
Java运行时之外的存储数据,常被称为堆外(off-heap)数据,在此之前,对于堆外数据的访问,Java平台一直没能提供令人满意的方案。ByteBuffer API安全,但处理效率不高,Unsafe高效,但不安全
外部函数
JNI可以调用原生代码,但是这远远不够,JNI执行过程包含不少架构,开发者需要在多个工具之间来回奔波。此外,JNI通常是与C++和C语言库交互,对其它开发语言支持甚少
基于以上这些痛点,在这个JEP中,千呼万唤,FFM API出现了
这个JEP的4个目标如下
核心的API和功能如下
这些API统称为FFM API,位于java.base模块的java.lang.foreign包中
由于这一组API比较多,且相对复杂,这里用官网给出的一个简单例子来演示
package com.jasonidea.jdk19;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import static java.lang.foreign.MemoryLayout.PathElement;
public class Jep424Demo {
public static void main(String[] args) {
new Jep424Demo().memoryOperation();
}
public void memoryOperation() {
/*
* 1. 创建结构化的顺序内存布局,结构如下
* struct Point {
* int x;
* int y;
* } pts[10];
*/
SequenceLayout ptsLayout = MemoryLayout.sequenceLayout(10, MemoryLayout.structLayout(
JAVA_INT.withName("x"),
JAVA_INT.withName("y")));
// 2. 分配内存并对内存设置值
VarHandle xHandle = ptsLayout.varHandle(PathElement.sequenceElement(), PathElement.groupElement("x"));
VarHandle yHandle = ptsLayout.varHandle(PathElement.sequenceElement(), PathElement.groupElement("y"));
MemorySegment segment = MemorySegment.allocateNative(ptsLayout, MemorySession.openImplicit());
for (int i = 0; i < ptsLayout.elementCount(); i++) {
xHandle.set(segment,/* index */ (long) i, /* value to write */i); // x
yHandle.set(segment,/* index */ (long) i, /* value to write */i); // y
System.out.printf("index => %d, x = %d, y = %d\n", i, i, i);
}
// 3. 获取内存值
int xValue = (int) xHandle.get(segment, 3);
System.out.println("Point[3].x = " + xValue);
int yValue = (int) yHandle.get(segment, 6);
System.out.println("Point[6].y = " + yValue);
}
}
输出结果如下
index => 0, x = 0, y = 0
index => 1, x = 1, y = 1
index => 2, x = 2, y = 2
index => 3, x = 3, y = 3
index => 4, x = 4, y = 4
index => 5, x = 5, y = 5
index => 6, x = 6, y = 6
index => 7, x = 7, y = 7
index => 8, x = 8, y = 8
index => 9, x = 9, y = 9
Point[3].x = 3
Point[6].y = 6
向量API目前是第四次孵化,功能是表达向量计算,在运行时编译为CPU架构上的最佳向量指令,从而实现优于等效标量计算的性能。目前相关API都在jdk.incubator.vector包下
来看一下官网给出的示例,并打印了一下标量计算和向量计算的执行时间
注意:要在项目中运行处于孵化阶段的特性代码,可以参考这篇文章。简单来说,孵化特性并不在JDK核心模块中,而是在jdk.incubator.xxx模块下
可以在项目中增加module-info.java文件
module ModuleInfo {
requires jdk.incubator.vector;
requires jdk.incubator.concurrent;
}
package com.jasonidea.jdk19;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorSpecies;
import java.util.Arrays;
public class Jep426Demo {
static void scalarComputation(float[] a, float[] b, float[] c) {
for (int i = 0; i < a.length; i++) {
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
}
}
static final VectorSpecies SPECIES = FloatVector.SPECIES_PREFERRED;
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[] tempA = {1.0f, 3.0f, 2.0f, 4.0f, 8.0f, 10.0f};
float[] tempB = {1.0f, -1.0f, 5.0f, 3.0f, 8.0f, 9.0f};
float[] tempC = {1.0f, 6.0f, 1.0f, 1.0f, 1.0f, 1.0f};
int scaleUpFactor = 10000;
float[] a = new float[tempA.length * scaleUpFactor];
float[] b = new float[tempA.length * scaleUpFactor];
float[] c = new float[tempA.length * scaleUpFactor];
for (int i = 0; i < scaleUpFactor; i++) {
for (int j = 0; j < tempA.length; j++) {
int idx = i * tempA.length + j;
a[idx] = tempA[j];
b[idx] = tempB[j];
c[idx] = tempC[j];
}
}
long startScalar = System.nanoTime();
scalarComputation(a, b, c);
System.out.printf("scalar computation cost %d nanoseconds\n", System.nanoTime() - startScalar);
long startVector = System.nanoTime();
vectorComputation(a, b, c);
System.out.printf("vector computation cost %d nanoseconds\n", System.nanoTime() - startVector);
}
}
运行结果如下
发现向量API的执行时间更长,足足差了40多倍,Why? 说好的在运行时编译为CPU架构上的最佳向量指令呢?
继续查看官网的说明,向量API有如下两种实现
这是处于预览阶段的特性,虚拟线程,也就是轻量级线程。虚拟线程极大地降低了高吞吐量应用的开发和维护成本
平台线程(原有线程)是在OS线程上做的封装,它的创建和切换成本很高,可用的线程数量也有限制。对于并发较高的应用,想要提高系统的吞吐量,之前一般是做异步化,但这种方式很难定位线上问题
虚拟线程的引入,让thread-per-request风格再次回到开发者的视线,虚拟线程的资源分配和调度由Java平台实现,它不再直接与OS线程强关联,而是直接将平台线程作为载体线程,这使得虚拟线程的可用数量大大增加
先举一个虚拟线程的简单使用示例
public class Jep425Demo {
// 创建10000个虚拟线程
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
} // try-with-resources,会隐式调用executor.close()
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
firstVirtualThread();
System.out.printf("firstVirtualThread finished, time cost %d ms\n",
System.currentTimeMillis() - startTime);
}
}
这段代码创建了10000个虚拟线程并开始执行,各自睡眠1秒后结束。现代操作系统可以轻松地支持10000个虚拟线程并发执行,虚拟机只需要与很少的OS线程交互,或许只有1个。执行结果如下,在大约1秒后,程序结束
平台线程示例如下
private static void testPlatformThread() {
try (var executor = Executors.newThreadPerTaskExecutor(Thread.ofPlatform().factory())) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
}
}
点击运行,直接资源不足了。根据错误日志,当前已经创建了4065个线程,第4066个就上限了
注意:这并不意味着虚拟线程比平台线程执行代码的速度更快 ,虚拟线程提供更高的吞吐量,而不是速度更快
但是,虚拟线程在如下两种场景下,才能大幅提高应用系统的吞吐量
虚拟线程创建方式
private static void infoCurrentThread() {
Thread thread = Thread.currentThread();
System.out.printf("线程名称: %s,是否虚拟线程: %s\n",
thread.getName(), thread.isVirtual());
}
private static void waysToCreateVirtualThread() {
// 方式一:直接启动,虚拟线程名称为""
Thread.startVirtualThread(() -> infoCurrentThread());
// 方式二:Builder模式构建
Thread vt = Thread.ofVirtual().allowSetThreadLocals(false)
.name("VirtualWorker-", 0)
.inheritInheritableThreadLocals(false)
.unstarted(() -> infoCurrentThread());
vt.start();
// 方式三:Factory模式构建
ThreadFactory factory = Thread.ofVirtual().allowSetThreadLocals(false)
.name("VirtualFactoryWorker-", 0)
.inheritInheritableThreadLocals(false)
.factory();
Thread virtualWorker = factory.newThread(() -> infoCurrentThread());
virtualWorker.start();
// 方式四:newVirtualThreadPerTaskExecutor
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> infoCurrentThread());
}
// 方式五:构建"虚拟线程池"
ExecutorService executorService = Executors.newThreadPerTaskExecutor(factory);
executorService.submit(() -> infoCurrentThread());
infoCurrentThread();
}
结构化并发功能还处于孵化阶段,该功能旨在简化多线程编程。结构化并发提供的特性将在不同线程中运行的多个任务视为一个工作单元,以简化错误处理和取消,提高了可靠性和可观测性
来看一个示例
package com.jasonidea.jdk19;
import jdk.incubator.concurrent.StructuredTaskScope;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class Jep428Demo {
record User(String name, Long id){}
record Order(String orderNo, Long id){}
record Response(User user, Order order){}
private User findUser(){
return new User("Java", 19L);
}
private Order fetchOrder(){
// return new Order("20221001", 1L);
throw new UnsupportedOperationException("fetchOrder");
}
private Response handle() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future user = scope.fork(() -> findUser());
Future order = scope.fork(() -> fetchOrder());
scope.join();
scope.throwIfFailed(); // 如果任意一个子任务失败,抛出异常
// 到这里时, 两个fork都执行成功了, 结果组合
return new Response(user.resultNow(), order.resultNow());
}
}
public static void main(String[] args) throws Exception {
Jep428Demo demo = new Jep428Demo();
demo.handle();
}
}
执行这段代码时,fetchOrder方法会抛出异常,throwIfFailed检测并重新抛出异常
// 修改fetchOrder方法,不抛出异常
private Order fetchOrder(){
// throw new UnsupportedOperationException("fetchOrder");
return new Order("20221001", 1L);
}
修改fetchOrder方法后,再次执行,程序正常退出
以上就是Java 19新特性初体验的详细内容。虽然新特性不多,而且大多还处在预览和孵化阶段,但这些特性还是值得期待的(PS:产品经理稳住,快跟不上了~_~)
Oracle - The Arrival of Java 19
JEP 395 - Records
JEP 405 - Record Patterns (Preview)
JEP 422 - Linux/RISC-V Port
JEP 427 - Pattern Matching for switch (Third Preview)
JEP 424 - Foreign Function & Memory API (Preview)
JEP 425 - Virtual Threads (Preview)
JEP 428 - Structured Concurrency (Incubator)
pakage jdk.incubator.foreign is not visible error