概述
JDK 20 于 2023 年 3 月 21 日正式发布。
JEP(Java Enhancement Proposal)Java增强提案
CSR(Compatibility & Specification Review) 兼容性和规范审查
变动说明
官网:
更多参考:
docs.oracle.com/en/java/jav… 更多版本:docs.oracle.com/en/java/jav…
重要变更和信息
JDK 20 包含 7 个 新特性 ,分别为:
- JEP 429: Scoped Values (Incubator) 作用域值(孵化特性)
- JEP 432: Record Patterns (Second Preview) Record 模式(第二次预览)
- JEP 433: Pattern Matching for switch (Fourth Preview)
Switch的模式匹配(第四次预览) - JEP 434: Foreign Function & Memory API (Second Preview) 外部函数与内存 API(第二次预览)
- JEP 436: Virtual Threads (Second Preview) 虚拟线程(第二次预览)
- JEP 437: Structured Concurrency (Second Incubator) 结构化并发(第二次孵化)
- JEP 438: Vector API (Fifth Incubator) 向量 API(第五次孵化)
而其中与开发过程中直接相关的特性主要包括:JEP 429(作用域值(孵化特性))、JEP 432(Record 模式(第二次预览))、JEP 433(Switch的模式匹配(第四次预览))、JEP 436(虚拟线程(第二次预览))等。
下载地址
你可以从这个链接下载Oracle JDK版本,更多版本下载。
也可以从这个链接下载生产就绪的OpenJDK版本。文件为压缩包,解压并设置环境变量就可以使用。
Java20新特性总结
1、JEP 433: Switch 的模式匹配(第四次预览)
JEP 433 (specification/language)
这仍然是一个预览特性
功能进化
switch表达式的功能进化
| java版本 | 特性类型 | JEP | 特性 |
|---|---|---|---|
| Java 5 | 首次引入,仅支持整型数据类型(如 byte, short, char, 和 int)及枚举类型 | ||
| Java 7 | 支持 String 类型 | ||
| Java 12 | 预览特性 | JEP 325 | 支持Switch表达式(箭头函数) |
| Java 13 | 预览特性 | JEP 354 | 加入 yield 语句来替代 break 语句,用于从 switch 表达式返回值 |
| Java 14 | 正式特性 | JEP 361 | 前2个版本的新特性转为正式特性 |
| Java 17 | 预览特性 | JEP 406 | 引入模式匹配的Switch表达式作为预览特性 |
| Java 18 | 第二次预览 | JEP 420 | 调整优化 |
| Java 19 | 第三次预览 | JEP 427 | 调整优化 |
| Java 20 | 第四次预览 | JEP 433 | 调整优化 |
在 Java 16 中, JEP 394 将 instanceof的模式匹配发布为正式属性。虽然可以不需要强制转换了,但是仍然需要大量的 if...else。而 Switch 表达式虽然简化了if...else,但是它无法像instanceof一样不需要强制转换。为了解决这个痛点,Java 17引入模式匹配的Switch表达式特性 ,目前该特性为预览特性。
该特性扩展了 switch 表达式和语句,允许它们使用模式匹配,这就意味着我们可以在 switch 的 case 标签中使用模式,如类型模式,使得代码更加灵活和表达性更强。而且也无需进行显式的类型转换了。例如,可以使用 case Integer i 这样的语法来匹配并自动转换类型。
但是,不知道小伙伴们注意没有,Switch 表达式只有一种类型,比如:我有一个诗人类(Poet),它有3个实现类:唐朝诗人(TangPoet)、宋朝诗人(SongPoet)、汉朝诗人(HanPoet),我要根据诗人的类型进行不同处理 :
Poet poet = ... // 诗人
switch (poet.getClass().getName()) {
case "my.poet.TangPoet":
TangPoet tp = (TangPoet) obj;
// 处理唐朝诗人
break;
case "my.poet.SongPoet":
SongPoet sp = (SongPoet) obj;
// 处理宋朝诗人
break;
case "my.poet.HanPoet":
HanPoet hp = (HanPoet) obj;
// 处理汉朝诗人
break;
// 其他类型
}
这个强转显然比较麻烦。所以,参考Java 17中,参考instanceof的模式匹配,为switch表达式引入了模式匹配功能作为预览特性。
Switch 模式匹配
在 Java 17 中,switch 表达式允许使用模式匹配来处理对象类型,这样就可以直接在 switch 语句中检查和转换类型,而不需要额外的 if...else 结构和显式类型转换。
case后面可以跟的标签主要有:
- 类型标签
- null标签
- 守卫标签
- enum或常量值
类型标签
允许在 switch 语句的 case 分支中直接匹配对象的类型。例如,case String s 允许你在该分支中直接作为字符串类型的 s 来使用,避免了显式的类型检查和强制类型转换。
举个例子:
@Test
public void switchTest() {
// 不是用switch根据类型判断
Object[] objects = { "Hello", "Java", "17", 666, 0.618 };
for (Object obj : objects) {
if (obj instanceof Integer v) {
System.out.printf("为整型 :%s %n", v);
} else if (obj instanceof Float v) {
System.out.printf("为Float:%s %n", v);
} else if (obj instanceof Double v) {
System.out.printf("为Double:%s %n", v);
} else if (obj instanceof String v) {
System.out.printf("为字符串:%s %n", v);
} else {
System.out.printf("其他类型:%s %n", obj);
}
}
}
我们用 Switch 表达式来改造下:
@Test
public void switchTest() {
Object[] objects = { "Hello", 123, "World", "Java", 3.14, "skjava" };
for (Object obj: objects) {
switch (obj) {
case Integer v -> System.out.println("为整数型:" + v);
case Float v -> System.out.println("为浮点型:" + v);
case Double v -> System.out.println("为双精度浮点数:" + v);
case String v -> System.out.println("为字符串:" + v);
default -> System.out.println("其他类型:" + obj);
}
}
}
相比上面的 if...else 简洁了很多。同时在 Java 17之前,Switch选择器表达式只支持特定类型,即基本整型数据类型byte、short、char和int;对应的装箱形式Byte、Short、Character和Integer;String类;枚举类型。现在有了类型模式,Switch 表达式可以是任何类型啦。
null标签
当switch允许任何引用类型的选择器表达式,那么我们需要留意null的情况,在Java17之前,向switch语句传递一个null值,会抛出一个NullPointerException,现在可以通过类型模式,将 null 检查作为一个单独的case标签来处理,如下:
@Test
public void switchTest() {
Object[] objects = { "Hello", "Java", "17", 142857, 0.618 };
for (Object obj: objects) {
switch (obj) {
case Integer v -> System.out.println("为整数型:" + v);
case Float v -> System.out.println("为浮点型:" + v);
case Double v -> System.out.println("为双精度浮点数:" + v);
case String v -> System.out.println("为字符串:" + v);
case null -> System.out.println("为空值");
default -> System.out.println("其他类型:" + obj);
}
}
}
case null 可以直接匹配值为 null 的情况。
守卫标签
与匹配常量值的case标签不同,模式case标签可以对应多个变量值。这通常会导致switch规则右侧出现条件语句。
根据字符串长度判断诗句是五言绝句还是七言绝句,代码如下:
@Test
public void switchCaseCaseTest() {
String[] poems = { "千山鸟飞绝", "春城无处不飞花", "红豆生南国", "二月春风似剪刀","念奴娇" };
for (String poem : poems) {
switch (poem) {
case null -> System.out.println("为空值");
case String s -> {
if (s.length() == 5)
System.out.printf("五言绝句:%s%n", s);
else if (s.length() == 7)
System.out.printf("七言绝句:%s%n", s);
else
System.out.printf("不知道是啥:%s%n", s);
}
}
}
}
这里的问题是,使用单一模式(即类型)来区分case就只能判断一种情况。我们只能在模式匹配中再通过if……else判断来区分不同的情况,来对一个模式的细化。这时,我们可以是使用switch中的when子句指定模式case标签的条件,例如,case String s when if (s.length() == 5)。表示当类型为String并且字符串长度为5的时候,我们将这种case标签称为守卫case标签,将布尔表达式称为保护。
@Test
public void switchCaseCaseTest() {
String[] poems = { "千山鸟飞绝", "春城无处不飞花", "红豆生南国", "二月春风似剪刀","念奴娇" };
for (String poem : poems) {
switch (poem) {
case null -> System.out.println("为空值");
case String s when s.length() == 5 -> System.out.printf("五言绝句:%s%n", s);
case String s when s.length() == 7 -> System.out.printf("七言绝句:%s%n", s);
case String s -> System.out.printf("不知道是啥:%s%n", s); //剩余情况,仍然走这个
}
}
}
使用守卫标签,我们可以编写更灵活和表达性强的代码。
2、JEP 432:Record模式(第二次预览)
功能进化
switch表达式的功能进化
| java版本 | 特性类型 | JEP | 特性 |
|---|---|---|---|
| Java 19 | 预览特性 | JEP 405 | 引入 Record 模式作为预览特性 |
| Java 20 | 第二次预览 | JEP 432 | 调整优化 |
Java 14 引入预览特性 Record类提供一种简洁的语法来声明数据载体的不可变对象,主要是为了解决长期以来在 Java 中定义纯数据载体类时,代码过于繁琐的问题。在 Java 16 中转为正式特性。
模式匹配最初是用在instanceof上,在 Java 14 作为预览特性引入的,为了解决 instanceof 在做类型匹配时需要进行强制类型转换而导致的代码冗余。
Java 20 引入 Record 模式作为预览特性,它允许在instanceof操作中使用记录模式,直接解构和匹配记录中的字段。
比如有一个记录Record Point(int x, int y),可以使用 Record 模式直接检查和提取 x 和 y 值:
if (obj instanceof Point(int x, int y)) {
System.out.println(x+y);
}
可以看出,类型判断、类型转换、Record变量赋值一气呵成,极大地简化了代码结构。
该特性使Java 模式匹配能力得到进一步扩展。
3、JEP 429:作用域值(孵化特性)
功能进化
| java版本 | 特性类型 | JEP | 特性 |
|---|---|---|---|
| Java 20 | 孵化特性 | JEP 429 | 作用域值作为孵化特性引入 |
引入作用域值,使线程内部和线程之间能够共享不可变的数据。它们优先于线程化局部变量,尤其是在使用大量虚拟线程时。
4、JEP 436:虚拟线程(第二次预览)
JEP 436 (core-libs/java.lang)
功能进化
| java版本 | 特性类型 | JEP | 特性 |
|---|---|---|---|
| Java 19 | 预览特性 | JEP 425 | 引入了虚拟线程作为预览特性 |
| Java 20 | 第二次预览 | JEP 436 | 优化调整 |
将虚拟线程引入Java平台。虚拟线程是轻量级线程,可以显著减少编写、维护和观察高吞吐量并发应用程序的工作量。
虚拟线程是一种轻量级的线程,也就是我们俗称的协程。它的资源分配和调度由VM实现,而不是操作系统。虚拟线程的主要特点包括:
- 轻量级:与传统线程相比,它更轻量,创建和销毁的成本较低。
- 简化并发编程:由于不受操作系统线程数量的限制,我们可以为每个独立的任务创建一个虚拟线程,简化并发编程模型。
5、JEP 434:外部函数和内存 API(第二次预览)
JEP 434 (core-libs)
这是一个预览特性。
功能进化
| java版本 | 特性类型 | JEP | 特性 |
|---|---|---|---|
| Java 14 | 孵化特性 | JEP 370 | 引入了外部内存访问 API作为孵化特性 |
| Java 15 | 第二次孵化 | JEP 383 | 优化外部内存访问 API |
| Java 16 | 孵化特性 | JEP 389 | 引入了外部链接器 API |
| Java 16 | 第三次孵化 | JEP 393 | 功能优化 |
| Java 17 | 孵化特性 | JEP 412 | 引入了外部函数和内存 API |
| Java 18 | 第二次孵化 | JEP 419 | 改进优化 |
| Java 19 | 预览特性 | JEP 424 | 改进优化 |
| Java 20 | 第二次预览 | JEP 434 | 改进优化 |
外部函数和内存 API 是在 Java 17 中作为孵化器引入的,它提供对本机代码的静态类型的纯Java访问,其主要目的是改善 Java 与本地代码(如 C 或 C++)的互操作性。此API与Foreign-Memory API(JEP 393)一起,将大大简化绑定到本机库的错误处理过程。
一般Java想要调用本地代码需要使用Java Native Interface (JNI),但是JNI操作比较复杂而且性能有限。
外部函数和内存API提供了一套更简洁的API,用于调用本地函数和处理本地内存,降低了复杂性,而且还设计了更多的安全保护措施,降低了内存泄露和应用崩溃的风险。
主要是通过两个组件实现的:
- Foreign Function Interface (FFI) : 允许 Java 代码直接调用非 Java 代码,比如 C/C++ 代码。
- Foreign Memory Access API:提供了一种安全的方法来访问不受
JVM管理的内存。
6、JEP 437:结构化并发(第二次孵化)
这是一个孵化特性。
功能进化
| java版本 | 特性类型 | JEP | 特性 |
|---|---|---|---|
| Java 19 | 孵化特性 | JEP 428 | 引入了外部内存访问 API作为孵化特性 |
| Java 20 | 第二次孵化 | JEP 437 | 改进优化 |
通过引入用于结构化并发的API来简化多线程编程。结构化并发将在不同线程中运行的多个任务视为一个工作单元,从而简化错误处理和消除,提高可靠性,并增强可观察性。
7、JEP 438:向量API(第五次孵化)
JEP 438 (core-libs)
功能进化
| java版本 | 特性类型 | JEP | 特性 |
|---|---|---|---|
| Java 16 | 第一次孵化 | JEP 338 | 提供一个平台无关的方式来表达向量计算,能够充分利用现代处理器上的向量硬件指令。 |
| Java 17 | 第二次孵化 | JEP 414 | 改进 |
| Java 18 | 第三次孵化 | JEP 417 | 进一步增强 |
| Java 19 | 第四次孵化 | JEP 426 | 进一步增强 |
| Java 20 | 第五次孵化 | JEP 438 | 进一步增强 |
向量 API 是在 Java 16 中作为孵化特性引入的。
引入API来表示向量计算,这些向量计算在运行时可靠地编译为支持的CPU架构上的最佳向量指令,从而实现优于等效标量计算的性能。Java 18 对向量 API 进行了进一步的改进和增强,以更好地利用硬件功能,提高 Java 在数值计算和机器学习等领域的性能。
8、移除的APIs、工具、容器
参考:
Oracle JDK和OpenJDK之间的差异
尽管官方已经声明了让OpenJDK和Oracle JDK二进制文件尽可能接近的目标,但两者之间仍然存在一些差异。
目前的差异是:
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 17及之后的版本在Oracle No-Fee Terms and Conditions License协议下发布。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协议。