This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
---
|
||||
title: Java 新特性专题:Java 8 到 Java 26 重要特性梳理
|
||||
description: Java 新特性学习路线,梳理 Java 8 到 Java 26 的语言特性、标准库增强、JVM 改进、LTS 版本、Lambda、Stream、Record 和虚拟线程。
|
||||
category: Java
|
||||
tag:
|
||||
- Java
|
||||
- Java新特性
|
||||
- Java面试
|
||||
sitemap:
|
||||
changefreq: weekly
|
||||
priority: 0.9
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java新特性,Java8新特性,Java11新特性,Java17新特性,Java21新特性,Lambda,Stream,Optional,模块化,var,Record,Switch,虚拟线程,模式匹配
|
||||
---
|
||||
|
||||
Java 新特性不适合按版本机械背诵,更适合抓住“语言表达能力、标准库增强、并发模型、JVM 改进、长期支持版本”这几条主线。日常开发优先掌握 Java 8、11、17、21 等 LTS 版本中的稳定特性,再按需了解后续版本的预览和孵化特性。
|
||||
|
||||
## 适合谁看
|
||||
|
||||
- 想系统了解 Java 8 之后版本变化的 Java 开发者。
|
||||
- 准备 Java 新特性、LTS 版本差异、虚拟线程、Record、模式匹配等面试题的同学。
|
||||
- 负责 JDK 升级,需要判断哪些特性会影响项目代码和运行时表现的工程师。
|
||||
- 已经熟悉 Java 8,但对 Java 11、17、21 之后变化不够清楚的读者。
|
||||
|
||||
## 学习重点
|
||||
|
||||
- Java 8 的 Lambda、Stream、Optional、接口默认方法和新日期 API。
|
||||
- Java 9 的模块化,以及后续版本对语言语法和标准库的持续增强。
|
||||
- Java 11、17、21 等 LTS 版本中更值得优先掌握的稳定能力。
|
||||
- var、文本块、Record、Switch 表达式、密封类、模式匹配等语言层变化。
|
||||
- 虚拟线程、结构化并发、分代 ZGC、Foreign Function & Memory API 等运行时和并发相关变化。
|
||||
- 区分正式特性、预览特性、孵化特性,避免在生产升级中误判风险。
|
||||
|
||||
## 建议阅读顺序
|
||||
|
||||
1. [Java8 新特性实战](./java8-common-new-features.md):先掌握 Lambda、Stream、Optional、接口默认方法和新日期 API。
|
||||
2. [Java 9 新特性概览](./java9.md)、[Java 10 新特性概览](./java10.md):理解模块化和局部变量类型推断等基础变化。
|
||||
3. [Java 11 新特性概览(重要)](./java11.md):重点关注第一个 8 之后被广泛采用的 LTS 版本。
|
||||
4. [Java 17 新特性概览(重要)](./java17.md):掌握 Record、密封类、Switch、模式匹配等现代 Java 语法演进。
|
||||
5. [Java 21 新特性概览(重要)](./java21.md):重点学习虚拟线程、分代 ZGC、模式匹配和字符串模板等变化。
|
||||
6. 再按需阅读 [Java 22 & 23 新特性概览](./java22-23.md)、[Java 24 新特性概览](./java24.md)、[Java 25 新特性概览](./java25.md)、[Java 26 新特性概览](./java26.md)。
|
||||
|
||||
## 核心文章
|
||||
|
||||
### Java 8 基础能力
|
||||
|
||||
- [Java8 新特性实战](./java8-common-new-features.md):掌握 Lambda、函数式接口、Stream、Optional、接口默认方法和新日期 API。
|
||||
- [《Java8 指南》中文翻译](./java8-tutorial-translate.md):通过更系统的教程理解 Java 8 常用特性。
|
||||
|
||||
### 重要 LTS 版本
|
||||
|
||||
- [Java 11 新特性概览(重要)](./java11.md):关注 HTTP Client、字符串 API、集合 API、ZGC 实验特性等变化。
|
||||
- [Java 17 新特性概览(重要)](./java17.md):关注 Record、密封类、Switch 表达式、文本块和模式匹配相关能力。
|
||||
- [Java 21 新特性概览(重要)](./java21.md):关注虚拟线程、分代 ZGC、Record Pattern、Pattern Matching for switch 等特性。
|
||||
|
||||
### 按版本追踪
|
||||
|
||||
- [Java 9 新特性概览](./java9.md):理解模块化系统和 JShell。
|
||||
- [Java 10 新特性概览](./java10.md):了解局部变量类型推断和运行时改进。
|
||||
- [Java 12 & 13 新特性概览](./java12-13.md):了解 Switch 表达式、文本块等变化。
|
||||
- [Java 14 & 15 新特性概览](./java14-15.md):了解 Record、文本块、隐藏类等特性。
|
||||
- [Java 16 新特性概览](./java16.md):了解 Record 正式转正、Pattern Matching for instanceof 等变化。
|
||||
- [Java 18 新特性概览](./java18.md)、[Java 19 新特性概览](./java19.md)、[Java 20 新特性概览](./java20.md):跟进 UTF-8 默认字符集、虚拟线程预览、结构化并发等演进。
|
||||
- [Java 22 & 23 新特性概览](./java22-23.md)、[Java 24 新特性概览](./java24.md)、[Java 25 新特性概览](./java25.md)、[Java 26 新特性概览](./java26.md):了解较新版本中的预览、孵化和正式特性。
|
||||
|
||||
## 高频问题
|
||||
|
||||
- Java 8 为什么重要?Lambda 和 Stream 分别解决什么问题?
|
||||
- `Optional` 适合用在哪些场景?为什么不建议滥用?
|
||||
- Java 9 模块化解决了什么问题?
|
||||
- `var` 是动态类型吗?它适合在哪些场景使用?
|
||||
- Record 和普通 JavaBean 有什么区别?
|
||||
- Switch 表达式和传统 switch 有什么区别?
|
||||
- 密封类适合解决什么问题?
|
||||
- 模式匹配带来了哪些代码简化?
|
||||
- 虚拟线程适合什么场景?和平台线程有什么区别?
|
||||
- 生产升级 JDK 时,如何区分正式特性、预览特性和孵化特性?
|
||||
|
||||
## 相关专题
|
||||
|
||||
- [Java 知识体系](../)
|
||||
- [Java 基础专题](../basis/)
|
||||
- [Java 并发编程专题](../concurrent/)
|
||||
- [JVM 专题](../jvm/)
|
||||
- [Java IO 专题](../io/)
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
@@ -0,0 +1,130 @@
|
||||
---
|
||||
title: Java 10 新特性概览
|
||||
description: 概览 JDK 10 的主要更新,重点介绍 var 类型推断与其他平台改进。
|
||||
category: Java
|
||||
tag:
|
||||
- Java新特性
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java 10,JDK10,var 局部变量类型推断,垃圾回收改进,性能
|
||||
---
|
||||
|
||||
**Java 10** 发布于 2018 年 3 月 20 日,这是一个非 LTS(长期支持)版本,Oracle 仅提供六个月的支持。
|
||||
|
||||
下图是从 JDK 8 到 JDK 25 每个版本的更新带来的新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||||
|
||||
- [JEP 286: Local-Variable Type Inference(局部变量类型推断)](https://openjdk.org/jeps/286)
|
||||
- [JEP 304: Garbage-Collector Interface(垃圾回收器接口)](https://openjdk.org/jeps/304)
|
||||
- [JEP 307: Parallel Full GC for G1(G1 并行 Full GC)](https://openjdk.org/jeps/307)
|
||||
- [JEP 310: Application Class-Data Sharing(应用程序类数据共享)](https://openjdk.org/jeps/310)
|
||||
- [JEP 317: Experimental Java-Based JIT Compiler(实验性的基于 Java 的 JIT 编译器)](https://openjdk.org/jeps/317)
|
||||
|
||||
## JEP 286: Local-Variable Type Inference
|
||||
|
||||
由于太多 Java 开发者希望 Java 中引入局部变量类型推断,于是 Java 10 的时候它来了,也算是众望所归了!
|
||||
|
||||
Java 10 提供了 `var` 关键字声明局部变量。
|
||||
|
||||
```java
|
||||
var id = 0;
|
||||
var codefx = new URL("https://mp.weixin.qq.com/");
|
||||
var list = new ArrayList<>();
|
||||
var list = List.of(1, 2, 3);
|
||||
var map = new HashMap<String, String>();
|
||||
var p = Paths.of("src/test/java/Java9FeaturesTest.java");
|
||||
var numbers = List.of("a", "b", "c");
|
||||
for (var n : numbers)
|
||||
System.out.print(n+ " ");
|
||||
```
|
||||
|
||||
`var` 关键字只能用于带有构造器的局部变量和 for 循环中。
|
||||
|
||||
```java
|
||||
var count = null; //❌编译不通过,不能声明为 null
|
||||
var r = () -> Math.random();//❌编译不通过,不能声明为 Lambda表达式
|
||||
var array = {1, 2, 3};//❌编译不通过,不能声明数组
|
||||
```
|
||||
|
||||
`var` 并不会改变 Java 是一门静态类型语言的事实,编译器负责推断出类型。
|
||||
|
||||
另外,Scala 和 Kotlin 中已经有了 `val` 关键字 ( `final var` 组合关键字)。
|
||||
|
||||
## JEP 304: Garbage-Collector Interface
|
||||
|
||||
在早期的 JDK 结构中,组成垃圾收集器 (GC) 实现的组件分散在代码库的各个部分。 Java 10 通过引入一套纯净的垃圾收集器接口来将不同垃圾收集器的源代码分隔开。
|
||||
|
||||
## JEP 307: Parallel Full GC for G1
|
||||
|
||||
从 Java 9 开始 G1 就成了默认的垃圾回收器,G1 是以一种低延时的垃圾回收器来设计的,旨在避免进行 Full GC,但是 Java 9 的 G1 的 Full GC 依然是使用单线程去完成标记清除算法,这可能会导致垃圾回收器在无法回收内存的时候触发 Full GC。
|
||||
|
||||
为了最大限度地减少 Full GC 造成的应用停顿的影响,从 Java10 开始,G1 的 FullGC 改为并行的标记清除算法,同时会使用与年轻代回收和混合回收相同的并行工作线程数量,从而减少了 Full GC 的发生,以带来更好的性能提升、更大的吞吐量。
|
||||
|
||||
## JEP 310: **应用程序类数据共享(扩展 CDS 功能)**
|
||||
|
||||
在 Java 5 中就已经引入了类数据共享机制(Class Data Sharing,简称 CDS),允许将一组类预处理为共享归档文件,以便在运行时能够进行内存映射以减少 Java 程序的启动时间,当多个 Java 虚拟机(JVM)共享相同的归档文件时,还可以减少动态内存的占用量,同时减少多个虚拟机在同一个物理或虚拟的机器上运行时的资源占用。CDS 在当时还是 Oracle JDK 的商业特性。
|
||||
|
||||
Java 10 在现有的 CDS 功能基础上再次拓展,以允许应用类放置在共享存档中。CDS 特性在原来的 bootstrap 类基础之上,扩展加入了应用类的 CDS 为 (Application Class-Data Sharing,AppCDS) 支持,大大加大了 CDS 的适用范围。其原理为:在启动时记录加载类的过程,写入到文本文件中,再次启动时直接读取此启动文本并加载。设想如果应用环境没有大的变化,启动速度就会得到提升。
|
||||
|
||||
## JEP 317: **实验性的基于 Java 的 JIT 编译器**
|
||||
|
||||
Graal 是一个基于 Java 语言编写的 JIT 编译器,是 JDK 9 中引入的实验性 Ahead-of-Time (AOT) 编译器的基础。
|
||||
|
||||
Oracle 的 HotSpot VM 便附带两个用 C++ 实现的 JIT compiler:C1 及 C2。在 Java 10 (Linux/x64, macOS/x64) 中,默认情况下 HotSpot 仍使用 C2,但通过向 java 命令添加 `-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler` 参数便可将 C2 替换成 Graal。
|
||||
|
||||
## API 增强
|
||||
|
||||
并不是所有的 API 改动都会通过 JEP(Java Enhancement Proposal)来发布。
|
||||
|
||||
在 JDK 的开发流程中:**JEP** 通常用于重大的改变,例如引入新的语言特性(如 `var`)、新的 JVM 机制(如 ZGC)或者大规模的库重构。像 `List.copyOf()` 这种在现有类中增加几个静态方法的操作,通常被视为常规的库维护。它们由 JDK 开发者直接通过 **JBS (JDK Bug System)** 的工单(Ticket)进行提交和评审,然后随版本直接发布。
|
||||
|
||||
### 集合增强
|
||||
|
||||
`List`,`Set`,`Map` 提供了静态方法 `copyOf()` 返回入参集合的一个不可变拷贝。
|
||||
|
||||
```java
|
||||
static <E> List<E> copyOf(Collection<? extends E> coll) {
|
||||
return ImmutableCollections.listCopy(coll);
|
||||
}
|
||||
```
|
||||
|
||||
使用 `copyOf()` 创建的集合为不可变集合,不能进行添加、删除、替换、 排序等操作,不然会报 `java.lang.UnsupportedOperationException` 异常。 IDEA 也会有相应的提示。
|
||||
|
||||

|
||||
|
||||
并且,`java.util.stream.Collectors` 中新增了静态方法,用于将流中的元素收集为不可变的集合。
|
||||
|
||||
```java
|
||||
var list = new ArrayList<>();
|
||||
list.stream().collect(Collectors.toUnmodifiableList());
|
||||
list.stream().collect(Collectors.toUnmodifiableSet());
|
||||
```
|
||||
|
||||
### Optional 增强
|
||||
|
||||
`Optional` 新增了一个无参的 `orElseThrow()` 方法,作为带参数的 `orElseThrow(Supplier<? extends X> exceptionSupplier)` 的简化版本,在没有值时默认抛出一个 NoSuchElementException 异常。
|
||||
|
||||
```java
|
||||
Optional<String> optional = Optional.empty();
|
||||
String result = optional.orElseThrow();
|
||||
```
|
||||
|
||||
## 其他
|
||||
|
||||
- **线程-局部管控**:Java 10 中线程管控引入 JVM 安全点的概念,将允许在不运行全局 JVM 安全点的情况下实现线程回调,由线程本身或者 JVM 线程来执行,同时保持线程处于阻塞状态,这种方式使得停止单个线程变成可能,而不是只能启用或停止所有线程
|
||||
- **备用存储装置上的堆分配**:Java 10 中将使得 JVM 能够使用适用于不同类型的存储机制的堆,在可选内存设备上进行堆内存分配
|
||||
- ……
|
||||
|
||||
## 参考
|
||||
|
||||
- Java 10 Features and Enhancements : <https://howtodoinjava.com/java10/java10-features/>
|
||||
|
||||
- Guide to Java10 : <https://www.baeldung.com/java-10-overview>
|
||||
|
||||
- 4 Class Data Sharing : <https://docs.oracle.com/javase/10/vm/class-data-sharing.htm#JSJVM-GUID-7EAA3411-8CF0-4D19-BD05-DF5E1780AA91>
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
@@ -0,0 +1,146 @@
|
||||
---
|
||||
title: Java 11 新特性概览(重要)
|
||||
description: 总结 JDK 11 的更新,关注新 HTTP 客户端与字符串增强等实用特性。
|
||||
category: Java
|
||||
tag:
|
||||
- Java新特性
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java 11,JDK11,LTS,HTTP 客户端,字符串 API,移除特性
|
||||
---
|
||||
|
||||
Java 11 于 2018 年 9 月 25 日正式发布,这是很重要的一个版本!Java 11 是继 Java 8 之后的第一个长期支持(Long-Term-Support)版本,Oracle 表示会对 Java 11 提供大力支持,这一支持将会持续至 2026 年 9 月。
|
||||
|
||||
下面这张图是 Oracle 官方给出的 Oracle JDK 支持的时间线。
|
||||
|
||||

|
||||
|
||||
下图是从 JDK 8 到 JDK 25 每个版本的更新带来的新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||||
|
||||
- [JEP 321: HTTP Client (Standard)](https://openjdk.org/jeps/321)
|
||||
- [JEP 323: Local-Variable Syntax for Lambda Parameters](https://openjdk.org/jeps/323)
|
||||
- [JEP 330: Launch Single-File Source-Code Programs](https://openjdk.org/jeps/330)
|
||||
- [JEP 333: ZGC: A Scalable Low-Latency Garbage Collector (Experimental)](https://openjdk.org/jeps/333)
|
||||
|
||||
## JEP 321: HTTP Client(HTTP 客户端,标准版)
|
||||
|
||||
Java 11 对 Java 9 中引入并在 Java 10 中进行了更新的 HTTP Client API 进行了标准化,在前两个版本中进行孵化的同时,HTTP Client 几乎被完全重写,并且现在完全支持异步非阻塞。
|
||||
|
||||
并且,Java 11 中,HTTP Client 的包名由 `jdk.incubator.http` 改为 `java.net.http`,该 API 通过 `CompletableFuture` 提供非阻塞请求和响应语义。使用起来也很简单,如下:
|
||||
|
||||
```java
|
||||
var request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("https://javastack.cn"))
|
||||
.GET()
|
||||
.build();
|
||||
var client = HttpClient.newHttpClient();
|
||||
|
||||
// 同步
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println(response.body());
|
||||
|
||||
// 异步
|
||||
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
|
||||
.thenApply(HttpResponse::body)
|
||||
.thenAccept(System.out::println);
|
||||
```
|
||||
|
||||
## JEP 333: ZGC(可扩展的低延迟垃圾收集器,实验性)
|
||||
|
||||
**ZGC 即 Z Garbage Collector**,是一个可伸缩的、低延迟的垃圾收集器。
|
||||
|
||||
ZGC 主要为了满足如下目标进行设计:
|
||||
|
||||
- GC 停顿时间不超过 10ms
|
||||
- 既能处理几百 MB 的小堆,也能处理几个 TB 的大堆
|
||||
- 应用吞吐能力不会下降超过 15%(与 G1 回收算法相比)
|
||||
- 方便在此基础上引入新的 GC 特性和利用 colored 针以及 Load barriers 优化奠定基础
|
||||
- 当前只支持 Linux/x64 位平台
|
||||
|
||||
ZGC 目前 **处在实验阶段**,只支持 Linux/x64 平台。注意:ZGC 在 Java 15 成为正式特性,在 Java 21 引入分代 ZGC。
|
||||
|
||||
与 CMS 中的 ParNew 和 G1 类似,ZGC 也采用标记-复制算法,不过 ZGC 对该算法做了重大改进。
|
||||
|
||||
在 ZGC 中出现 Stop The World 的情况会更少!
|
||||
|
||||
详情可以看:[《新一代垃圾回收器 ZGC 的探索与实践》](https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html)
|
||||
|
||||
## JEP 323: Local-Variable Syntax for Lambda Parameters(Lambda 参数的局部变量语法)
|
||||
|
||||
从 Java 10 开始,便引入了局部变量类型推断这一关键特性。类型推断允许使用关键字 var 作为局部变量的类型而不是实际类型,编译器根据分配给变量的值推断出类型。
|
||||
|
||||
Java 10 中对 var 关键字存在几个限制
|
||||
|
||||
- 只能用于局部变量上
|
||||
- 声明时必须初始化
|
||||
- 不能用作方法参数
|
||||
- 不能在 Lambda 表达式中使用
|
||||
|
||||
Java11 开始允许开发者在 Lambda 表达式中使用 var 进行参数声明。
|
||||
|
||||
```java
|
||||
// 下面两者是等价的
|
||||
Consumer<String> consumer = (var i) -> System.out.println(i);
|
||||
Consumer<String> consumer = (String i) -> System.out.println(i);
|
||||
```
|
||||
|
||||
## JEP 330: Launch Single-File Source-Code Programs(启动单文件源代码程序)
|
||||
|
||||
这意味着我们可以运行单一文件的 Java 源代码。此功能允许使用 Java 解释器直接执行 Java 源代码。源代码在内存中编译,然后由解释器执行,不需要在磁盘上生成 `.class` 文件了。唯一的约束在于所有相关的类必须定义在同一个 Java 文件中。
|
||||
|
||||
对于 Java 初学者并希望尝试简单程序的人特别有用,并且能和 jshell 一起使用,一定程度上增强了使用 Java 来写脚本程序的能力。
|
||||
|
||||
## API 增强
|
||||
|
||||
并不是所有的 API 改动都会通过 JEP(Java Enhancement Proposal)来发布。
|
||||
|
||||
在 JDK 的开发流程中:**JEP** 通常用于重大的改变,例如引入新的语言特性(如 `var`)、新的 JVM 机制(如 ZGC)或者大规模的库重构。像 `String.isBlank()` 这种在现有类中增加几个方法的操作,通常被视为常规的库维护。它们由 JDK 开发者直接通过 **JBS (JDK Bug System)** 的工单(Ticket)进行提交和评审,然后随版本直接发布。
|
||||
|
||||
### String 增强
|
||||
|
||||
Java 11 增加了一系列的字符串处理方法:
|
||||
|
||||
```java
|
||||
//判断字符串是否为空
|
||||
" ".isBlank();//true
|
||||
//去除字符串首尾空格
|
||||
" Java ".strip();// "Java"
|
||||
//去除字符串首部空格
|
||||
" Java ".stripLeading(); // "Java "
|
||||
//去除字符串尾部空格
|
||||
" Java ".stripTrailing(); // "Java"
|
||||
//重复字符串多少次
|
||||
"Java".repeat(3); // "JavaJavaJava"
|
||||
//返回由行终止符分隔的字符串集合。
|
||||
"A\nB\nC".lines().count(); // 3
|
||||
"A\nB\nC".lines().collect(Collectors.toList());
|
||||
```
|
||||
|
||||
### Optional 增强
|
||||
|
||||
新增了 `isEmpty()` 方法来判断指定的 `Optional` 对象是否为空。
|
||||
|
||||
```java
|
||||
var op = Optional.empty();
|
||||
System.out.println(op.isEmpty());//判断指定的 Optional 对象是否为空
|
||||
```
|
||||
|
||||
## 其他新特性
|
||||
|
||||
- **新的垃圾回收器 Epsilon**:一个完全消极的 GC 实现,分配有限的内存资源,最大限度的降低内存占用和内存吞吐延迟时间
|
||||
- **低开销的 Heap Profiling**:Java 11 中提供一种低开销的 Java 堆分配采样方法,能够得到堆分配的 Java 对象信息,并且能够通过 JVMTI 访问堆信息
|
||||
- **TLS1.3 协议**:Java 11 中包含了传输层安全性(TLS)1.3 规范(RFC 8446)的实现,替换了之前版本中包含的 TLS,包括 TLS 1.2,同时还改进了其他 TLS 功能,例如 OCSP 装订扩展(RFC 6066,RFC 6961),以及会话散列和扩展主密钥扩展(RFC 7627),在安全性和性能方面也做了很多提升
|
||||
- **飞行记录器(Java Flight Recorder)**:飞行记录器之前是商业版 JDK 的一项分析工具,但在 Java 11 中,其代码被包含到公开代码库中,这样所有人都能使用该功能了。
|
||||
- ......
|
||||
|
||||
## 参考
|
||||
|
||||
- JDK 11 Release Notes:<https://www.oracle.com/java/technologies/javase/11-relnote-issues.html>
|
||||
- Java 11 – Features and Comparison:<https://www.geeksforgeeks.org/java-11-features-and-comparison/>
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
@@ -0,0 +1,334 @@
|
||||
---
|
||||
title: Java 12 & 13 新特性概览
|
||||
description: 归纳 JDK 12/13 的特性更新,包含字符串增强、switch 改进与 GC 调整等。
|
||||
category: Java
|
||||
tag:
|
||||
- Java新特性
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java 12,Java 13,字符串增强,切换表达式,垃圾回收,JEP
|
||||
---
|
||||
|
||||
## Java 12
|
||||
|
||||
JDK 12 于 2019 年 3 月 19 日发布,这是一个非 LTS 版本。
|
||||
|
||||
这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||||
|
||||
- [JEP 189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental)](https://openjdk.org/jeps/189)
|
||||
- [JEP 325: Switch Expressions (Preview) (switch 表达式, 预览特性)](https://openjdk.org/jeps/325)
|
||||
- [JEP 334: JVM Constants API (JVM 常量 API)](https://openjdk.org/jeps/334)
|
||||
- [JEP 344: Abortable Mixed Collections for G1 (G1 可中止的混合收集集合)](https://openjdk.org/jeps/344)
|
||||
- [JEP 346: Promptly Return Unused Committed Memory (G1 及时返回未使用的已分配内存)](https://openjdk.org/jeps/346)
|
||||
|
||||
下图是从 JDK 8 到 JDK 25 每个版本的更新带来的新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
### JEP 189: Shenandoah(低延迟垃圾收集器,实验性)
|
||||
|
||||
Redhat 主导开发的 Pauseless GC 实现,主要目标是 99.9% 的暂停小于 10ms,暂停与堆大小无关等
|
||||
|
||||
和 Java11 开源的 ZGC 相比(需要升级到 JDK11 才能使用),Shenandoah GC 有稳定的 JDK8u 版本,在 Java8 占据主要市场份额的今天有更大的可落地性。
|
||||
|
||||
### JEP 344 & JEP 346: G1 收集器优化
|
||||
|
||||
Java12 为默认的垃圾收集器 G1 带来了两项更新:
|
||||
|
||||
- **可中止的混合收集集合**:JEP344 的实现,为了达到用户提供的停顿时间目标,JEP 344 通过把要被回收的区域集(混合收集集合)拆分为强制和可选部分,使 G1 垃圾回收器能中止垃圾回收过程。 G1 可以中止可选部分的回收以达到停顿时间目标
|
||||
- **及时返回未使用的已分配内存**:JEP346 的实现,增强 G1 GC,以便在空闲时自动将 Java 堆内存返回给操作系统
|
||||
|
||||
### JEP 334: JVM Constants API(JVM 常量 API)
|
||||
|
||||
引入了一个 API 来对关键类文件和运行时工件的名义描述进行建模,特别是可以从常量池加载的常量。
|
||||
|
||||
这个 API 提供了一组接口和工具类,用于表示和操作类文件中的常量池条目。它主要包括:
|
||||
|
||||
- **常量描述符接口**:`ConstantDesc` 接口及其子接口,用于描述各种类型的常量
|
||||
- **常量值类型**:`ClassDesc`、`MethodTypeDesc`、`MethodHandleDesc`、`DynamicConstantDesc` 等
|
||||
- **引导方法**:支持 `invokedynamic` 指令和常量动态引导方法
|
||||
|
||||
这个 API 主要是为了支持以下场景:
|
||||
|
||||
1. **类文件操作**:提供了一种标准化的方式来描述和操作类文件中的常量池
|
||||
2. **字节码生成**:简化了字节码生成框架(如 ASM)与 Java 代码的交互
|
||||
3. **反射增强**:使得反射操作更加类型安全和表达力更强
|
||||
4. **编译器工具**:为编译器和代码生成工具提供了更好的抽象
|
||||
|
||||
这个 API 是 Java 12 中重要的底层改进,为后续的字节码操作和编译器特性奠定了基础。
|
||||
|
||||
### JEP 325: Switch Expressions(switch 表达式,预览)
|
||||
|
||||
传统的 `switch` 语法存在容易漏写 `break` 的问题,而且从代码整洁性层面来看,多个 break 本质也是一种重复。
|
||||
|
||||
Java12 增强了 `switch` 表达式,使用类似 lambda 语法条件匹配成功后的执行块,不需要多写 break。
|
||||
|
||||
```java
|
||||
switch (day) {
|
||||
case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
|
||||
case TUESDAY -> System.out.println(7);
|
||||
case THURSDAY, SATURDAY -> System.out.println(8);
|
||||
case WEDNESDAY -> System.out.println(9);
|
||||
}
|
||||
```
|
||||
|
||||
## Java 13
|
||||
|
||||
JDK 13 于 2019 年 9 月 17 日发布,这是一个非 LTS 版本。
|
||||
|
||||
这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||||
|
||||
- [JEP 350: Dynamic CDS Archives (动态 CDS 存档)](https://openjdk.org/jeps/350)
|
||||
- [JEP 351: ZGC: Uncommit Unused Memory (ZGC 释放未使用内存)](https://openjdk.org/jeps/351)
|
||||
- [JEP 355: Text Blocks (Preview) (文本块, 预览特性)](https://openjdk.org/jeps/355)
|
||||
- [JEP 354: Switch Expressions (Second Preview) (switch 表达式, 第二次预览)](https://openjdk.org/jeps/354)
|
||||
|
||||
下图是从 JDK 8 到 JDK 24 每个版本的更新带来的新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
### JEP 351: ZGC(释放未使用内存)
|
||||
|
||||
在 Java 11 中实验性引入的 ZGC 在实际的使用中存在未能主动将未使用的内存释放给操作系统的问题。
|
||||
|
||||
ZGC 堆由一组称为 ZPages 的堆区域组成。在 GC 周期中清空 ZPages 区域时,它们将被释放并返回到页面缓存 **ZPageCache** 中,此缓存中的 ZPages 按最近最少使用(LRU)的顺序,并按照大小进行组织。
|
||||
|
||||
在 Java 13 中,ZGC 将向操作系统返回被标识为长时间未使用的页面,这样它们将可以被其他进程重用。
|
||||
|
||||
### JEP 350: Dynamic CDS Archives(动态 CDS 存档)
|
||||
|
||||
Java 13 中对 Java 10 中引入的应用程序类数据共享(AppCDS)进行了进一步的简化、改进和扩展,即:**允许在 Java 应用程序执行结束时动态进行类归档**,具体能够被归档的类包括所有已被加载,但不属于默认基层 CDS 的应用程序类和引用类库中的类。
|
||||
|
||||
这提高了应用程序类数据共享([AppCDS](https://openjdk.java.net/jeps/310))的可用性。无需用户进行试运行来为每个应用程序创建类列表。
|
||||
|
||||
```bash
|
||||
java -XX:ArchiveClassesAtExit=my_app_cds.jsa -cp my_app.jar
|
||||
java -XX:SharedArchiveFile=my_app_cds.jsa -cp my_app.jar
|
||||
```
|
||||
|
||||
### JEP 355: Text Blocks(文本块,预览)
|
||||
|
||||
解决 Java 定义多行字符串时只能通过换行转义或者换行连接符来变通支持的问题,引入**三重双引号**来定义多行文本。
|
||||
|
||||
Java 13 支持两个 `"""` 符号中间的任何内容都会被解释为字符串的一部分,包括换行符。注意:这里的“两个”应理解为“一对”,即开始和结束各一个。
|
||||
|
||||
未支持文本块之前的 HTML 写法:
|
||||
|
||||
```java
|
||||
String json ="{\n" +
|
||||
" \"name\":\"mkyong\",\n" +
|
||||
" \"age\":38\n" +
|
||||
"}\n";
|
||||
```
|
||||
|
||||
支持文本块之后的 HTML 写法:
|
||||
|
||||
```java
|
||||
String json = """
|
||||
{
|
||||
"name":"mkyong",
|
||||
"age":38
|
||||
}
|
||||
""";
|
||||
```
|
||||
|
||||
未支持文本块之前的 SQL 写法:
|
||||
|
||||
```sql
|
||||
String query = "SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`\n" +
|
||||
"WHERE `CITY` = 'INDIANAPOLIS'\n" +
|
||||
"ORDER BY `EMP_ID`, `LAST_NAME`;\n";
|
||||
```
|
||||
|
||||
支持文本块之后的 SQL 写法:
|
||||
|
||||
```sql
|
||||
String query = """
|
||||
SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`
|
||||
WHERE `CITY` = 'INDIANAPOLIS'
|
||||
ORDER BY `EMP_ID`, `LAST_NAME`;
|
||||
""";
|
||||
```
|
||||
|
||||
文本块相关的方法(`formatted()`、`stripIndent()`、`translateEscapes()`)介绍请参见本文 [API 增强 - String 增强(文本块相关方法)](#string-增强文本块相关方法) 部分。
|
||||
|
||||
### JEP 354: Switch Expressions(switch 表达式,第二次预览)
|
||||
|
||||
`Switch` 表达式中就多了一个关键字用于跳出 `Switch` 块的关键字 `yield`,主要用于返回一个值
|
||||
|
||||
`yield` 和 `return` 的区别在于:`return` 会直接跳出当前循环或者方法,而 `yield` 只会跳出当前 `Switch` 块,同时在使用 `yield` 时,需要有 `default` 条件
|
||||
|
||||
```java
|
||||
private static String descLanguage(String name) {
|
||||
return switch (name) {
|
||||
case "Java": yield "object-oriented, platform independent and secured";
|
||||
case "Ruby": yield "a programmer's best friend";
|
||||
default: yield name +" is a good language";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## API 增强
|
||||
|
||||
并不是所有的 API 改动都会通过 JEP(Java Enhancement Proposal)来发布。
|
||||
|
||||
在 JDK 的开发流程中:**JEP** 通常用于重大的改变,例如引入新的语言特性(如 `switch` 表达式)、新的 JVM 机制(如 ZGC)或者大规模的库重构。像 `String.indent()` 这种在现有类中增加几个方法的操作,通常被视为常规的库维护。它们由 JDK 开发者直接通过 **JBS (JDK Bug System)** 的工单(Ticket)进行提交和评审,然后随版本直接发布。
|
||||
|
||||
### String 增强
|
||||
|
||||
Java 12 增加了两个的字符串处理方法。
|
||||
|
||||
#### indent() - 缩进方法
|
||||
|
||||
`indent()` 方法可以实现字符串缩进。
|
||||
|
||||
```java
|
||||
String text = "Java";
|
||||
// 缩进 4 格
|
||||
text = text.indent(4);
|
||||
System.out.println(text);
|
||||
text = text.indent(-10);
|
||||
System.out.println(text);
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
```plain
|
||||
Java
|
||||
Java
|
||||
```
|
||||
|
||||
#### transform() - 转换方法
|
||||
|
||||
`transform()` 方法可以用来转变指定字符串。
|
||||
|
||||
```java
|
||||
String result = "foo".transform(input -> input + " bar");
|
||||
System.out.println(result); // foo bar
|
||||
```
|
||||
|
||||
### Files 增强
|
||||
|
||||
Java 12 添加了 `mismatch()` 方法来比较两个文件:
|
||||
|
||||
```java
|
||||
public static long mismatch(Path path, Path path2) throws IOException
|
||||
```
|
||||
|
||||
`mismatch()` 方法用于比较两个文件,并返回第一个不匹配字符的位置,如果文件相同则返回 -1L。
|
||||
|
||||
代码示例(两个文件内容相同的情况):
|
||||
|
||||
```java
|
||||
Path filePath1 = Files.createTempFile("file1", ".txt");
|
||||
Path filePath2 = Files.createTempFile("file2", ".txt");
|
||||
Files.writeString(filePath1, "Java 12 Article");
|
||||
Files.writeString(filePath2, "Java 12 Article");
|
||||
|
||||
long mismatch = Files.mismatch(filePath1, filePath2);
|
||||
assertEquals(-1, mismatch);
|
||||
```
|
||||
|
||||
代码示例(两个文件内容不相同的情况):
|
||||
|
||||
```java
|
||||
Path filePath3 = Files.createTempFile("file3", ".txt");
|
||||
Path filePath4 = Files.createTempFile("file4", ".txt");
|
||||
Files.writeString(filePath3, "Java 12 Article");
|
||||
Files.writeString(filePath4, "Java 12 Tutorial");
|
||||
|
||||
long mismatch = Files.mismatch(filePath3, filePath4);
|
||||
assertEquals(8, mismatch);
|
||||
```
|
||||
|
||||
### NumberFormat 增强
|
||||
|
||||
Java 12 中 `NumberFormat` 新增了对复杂的数字进行格式化的支持:
|
||||
|
||||
```java
|
||||
NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
|
||||
String result = fmt.format(1000);
|
||||
System.out.println(result);
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
```plain
|
||||
1K
|
||||
```
|
||||
|
||||
### Socket API 增强
|
||||
|
||||
Java 13 将 Socket API 的底层进行了重写, `NioSocketImpl` 是对 `PlainSocketImpl` 的直接替代,它使用 `java.util.concurrent` 包下的锁而不是同步方法。如果要使用旧实现,请使用 `-Djdk.net.usePlainSocketImpl=true`。
|
||||
|
||||
并且,在 Java 13 中是默认使用新的 Socket 实现。
|
||||
|
||||
```java
|
||||
public final class NioSocketImpl extends SocketImpl implements PlatformSocketImpl {
|
||||
}
|
||||
```
|
||||
|
||||
### FileSystems 增强
|
||||
|
||||
Java 13 中 `FileSystems` 类中添加了以下三种新方法,以便更容易地使用将文件内容视为文件系统的文件系统提供程序:
|
||||
|
||||
- `newFileSystem(Path)`
|
||||
- `newFileSystem(Path, Map<String, ?>)`
|
||||
- `newFileSystem(Path, Map<String, ?>, ClassLoader)`
|
||||
|
||||
### String 增强(文本块相关方法)
|
||||
|
||||
Java 13 引入了文本块(Text Blocks)预览特性,`String` 类新增加了 3 个新的方法来操作文本块:
|
||||
|
||||
- `formatted(Object... args)`:它类似于 `String` 的 `format()` 方法。添加它是为了支持文本块的格式设置。
|
||||
- `stripIndent()`:用于去除文本块中每一行开头和结尾的空格。
|
||||
- `translateEscapes()`:转义序列如 _"\\\t"_ 转换为 _"\t"_
|
||||
|
||||
由于文本块是一项预览功能,可以在未来版本中删除,因此这些新方法被标记为弃用。
|
||||
|
||||
```java
|
||||
@Deprecated(forRemoval=true, since="13")
|
||||
public String stripIndent() {
|
||||
}
|
||||
@Deprecated(forRemoval=true, since="13")
|
||||
public String formatted(Object... args) {
|
||||
|
||||
}
|
||||
@Deprecated(forRemoval=true, since="13")
|
||||
public String translateEscapes() {
|
||||
}
|
||||
```
|
||||
|
||||
关于文本块的详细介绍,请参见本文 [JEP 355: Text Blocks (Preview)](#jep-355-text-blocks-preview) 部分。
|
||||
|
||||
## 补充
|
||||
|
||||
### 关于预览特性
|
||||
|
||||
先贴一段 oracle 官网原文:`This is a preview feature, which is a feature whose design, specification, and implementation are complete, but is not permanent, which means that the feature may exist in a different form or not at all in future JDK releases. To compile and run code that contains preview features, you must specify additional command-line options.`
|
||||
|
||||
这是一个预览功能,该功能的设计,规格和实现是完整的,但不是永久性的,这意味着该功能可能以其他形式存在或在将来的 JDK 版本中根本不存在。 要编译和运行包含预览功能的代码,必须指定其他命令行选项。
|
||||
|
||||
就以 `switch` 的增强为例子,从 Java 12 中推出,到 Java 13 中将继续增强,直到 Java 14 才正式转正进入 JDK 可以放心使用,不用考虑后续 JDK 版本对其的改动或修改。
|
||||
|
||||
一方面可以看出 JDK 作为标准平台在增加新特性的严谨态度,另一方面个人认为是对于预览特性应该采取审慎使用的态度。特性的设计和实现容易,但是其实际价值依然需要在使用中去验证
|
||||
|
||||
### JVM 虚拟机优化
|
||||
|
||||
每次 Java 版本的发布都伴随着对 JVM 虚拟机的优化,包括对现有垃圾回收算法的改进,引入新的垃圾回收算法,移除老旧的不再适用于今天的垃圾回收算法等
|
||||
|
||||
整体优化的方向是**高效,低时延的垃圾回收表现**
|
||||
|
||||
对于日常的应用开发者可能比较关注新的语法特性,但是从一个公司角度来说,在考虑是否升级 Java 平台时更加考虑的是**JVM 运行时的提升**
|
||||
|
||||
## 参考
|
||||
|
||||
- JDK Project Overview:<https://openjdk.java.net/projects/jdk/>
|
||||
- Oracle Java12 ReleaseNote:<https://www.oracle.com/java/technologies/javase/12all-relnotes.htm>
|
||||
- What is new in Java 12:<https://mkyong.com/java/what-is-new-in-java-12/>
|
||||
- Oracle Java13 ReleaseNote <https://www.oracle.com/technetwork/java/javase/13all-relnotes-5461743.html#NewFeature>
|
||||
- New Java13 Features <https://www.baeldung.com/java-13-new-features>
|
||||
- Java13 新特性概述 <https://www.ibm.com/developerworks/cn/java/the-new-features-of-Java-13/index.html>
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
@@ -0,0 +1,323 @@
|
||||
---
|
||||
title: Java 14 & 15 新特性概览
|
||||
description: 概览 JDK 14/15 的关键特性,如 record、文本块与空指针精准提示等语言增强。
|
||||
category: Java
|
||||
tag:
|
||||
- Java新特性
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java 14,Java 15,record,文本块,NullPointerException 细节,模式匹配,JEP
|
||||
---
|
||||
|
||||
## Java 14
|
||||
|
||||
JDK 14 于 2020 年 3 月 17 日发布,这是一个非 LTS 版本。
|
||||
|
||||
这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||||
|
||||
- [JEP 305: Pattern Matching for instanceof(instanceof 模式匹配,预览)](https://openjdk.org/jeps/305)
|
||||
- [JEP 358: Helpful NullPointerExceptions (空指针异常精准提示)](https://openjdk.org/jeps/358)
|
||||
- [JEP 361: Switch Expressions (Standard) (switch 表达式, 转正)](https://openjdk.org/jeps/361)
|
||||
- [JEP 359: Records (Preview) (record 关键字, 预览特性)](https://openjdk.org/jeps/359)
|
||||
- [JEP 368: Text Blocks (Second Preview) (文本块, 第二次预览)](https://openjdk.org/jeps/368)
|
||||
- [JEP 363: Remove the CMS Garbage Collector (移除 CMS 垃圾收集器)](https://openjdk.org/jeps/363)
|
||||
|
||||
下图是从 JDK 8 到 JDK 25 每个版本的更新带来的新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
### JEP 305: Pattern Matching for instanceof(instanceof 模式匹配,预览)
|
||||
|
||||
Java 14 继续将 instanceof 模式匹配作为预览特性,这是 Java 14 引入的功能(JEP 305)。
|
||||
|
||||
该特性允许在 instanceof 检查的同时进行类型转换,避免了显式强制转换的需要。注意:instanceof 模式匹配在 Java 14 是第一次预览(JEP 305),在 Java 15 是第二次预览(JEP 375),最终在 Java 16 转正(JEP 394)。
|
||||
|
||||
### JEP 358: Helpful NullPointerExceptions(空指针异常精准提示)
|
||||
|
||||
通过 JVM 参数中添加 `-XX:+ShowCodeDetailsInExceptionMessages`,可以在空指针异常中获取更为详细的调用信息,更快的定位和解决问题。
|
||||
|
||||
```java
|
||||
a.b.c.i = 99; // 假设这段代码会发生空指针
|
||||
```
|
||||
|
||||
Java 14 之前:
|
||||
|
||||
```java
|
||||
Exception in thread "main" java.lang.NullPointerException
|
||||
at NullPointerExample.main(NullPointerExample.java:5)
|
||||
```
|
||||
|
||||
Java 14 之后:
|
||||
|
||||
```java
|
||||
// 增加参数后提示的异常中很明确的告知了哪里为空导致
|
||||
Exception in thread "main" java.lang.NullPointerException:
|
||||
Cannot read field 'c' because 'a.b' is null.
|
||||
at Prog.main(Prog.java:5)
|
||||
```
|
||||
|
||||
### JEP 361: Switch Expressions(switch 表达式,标准版)
|
||||
|
||||
Java 12 引入的 switch(预览特性)在 Java 14 变为正式版本,不需要增加参数来启用,直接在 JDK 14 中就能使用。
|
||||
|
||||
Java 12 为 switch 表达式引入了类似 lambda 语法条件匹配成功后的执行块,不需要多写 break,Java 13 提供了 `yield` 来在 block 中返回值。
|
||||
|
||||
```java
|
||||
String result = switch (day) {
|
||||
case "M", "W", "F" -> "MWF";
|
||||
case "T", "TH", "S" -> "TTS";
|
||||
default -> {
|
||||
if(day.isEmpty())
|
||||
yield "Please insert a valid day.";
|
||||
else
|
||||
yield "Looks like a Sunday.";
|
||||
}
|
||||
|
||||
};
|
||||
System.out.println(result);
|
||||
```
|
||||
|
||||
### JEP 359: Records(record 类,预览)
|
||||
|
||||
`record` 关键字可以简化 **数据类**(一个 Java 类一旦实例化就不能再修改)的定义方式,使用 `record` 代替 `class` 定义的类,只需要声明属性,就可以在获得属性的访问方法,以及 `toString()`,`hashCode()`, `equals()` 方法。
|
||||
|
||||
类似于使用 `class` 定义类,同时使用了 lombok 插件,并打上了 `@Getter,@ToString,@EqualsAndHashCode` 注解。
|
||||
|
||||
```java
|
||||
/**
|
||||
* 这个类具有两个特征
|
||||
* 1. 所有成员属性都是final
|
||||
* 2. 全部方法由构造方法,和两个成员属性访问器组成(共三个)
|
||||
* 那么这种类就很适合使用record来声明
|
||||
*/
|
||||
final class Rectangle implements Shape {
|
||||
final double length;
|
||||
final double width;
|
||||
|
||||
public Rectangle(double length, double width) {
|
||||
this.length = length;
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
double length() { return length; }
|
||||
double width() { return width; }
|
||||
}
|
||||
/**
|
||||
* 1. 使用 record 声明的类会自动拥有上面类中的三个方法
|
||||
* 2. 在这基础上还附赠了 equals(),hashCode() 方法以及 toString() 方法
|
||||
* 3. toString 方法中包括所有成员属性的字符串表示形式及其名称
|
||||
*/
|
||||
record Rectangle(float length, float width) { }
|
||||
```
|
||||
|
||||
### JEP 368: Text Blocks(文本块,第二次预览)
|
||||
|
||||
Java14 中,文本块依然是预览特性,不过,其引入了两个新的转义字符:
|
||||
|
||||
- `\` : 表示行尾,不引入换行符
|
||||
- `\s`:表示单个空格
|
||||
|
||||
```java
|
||||
String str = "凡心所向,素履所往,生如逆旅,一苇以航。";
|
||||
|
||||
String str2 = """
|
||||
凡心所向,素履所往, \
|
||||
生如逆旅,一苇以航。""";
|
||||
System.out.println(str2);// 凡心所向,素履所往, 生如逆旅,一苇以航。
|
||||
String text = """
|
||||
java
|
||||
c++\sphp
|
||||
""";
|
||||
System.out.println(text);
|
||||
//输出:
|
||||
java
|
||||
c++ php
|
||||
```
|
||||
|
||||
### 其他特性
|
||||
|
||||
- 从 Java11 引入的 ZGC 作为继 G1 过后的下一代 GC 算法,从支持 Linux 平台到 Java14 开始支持 MacOS 和 Windows(个人感觉是终于可以在日常开发工具中先体验下 ZGC 的效果了,虽然其实 G1 也够用)
|
||||
- [JEP 363: Remove the CMS Garbage Collector](https://openjdk.org/jeps/363)(移除 CMS 垃圾收集器,功成而退)
|
||||
- 新增了 jpackage 工具,标配将应用打成 jar 包外,还支持不同平台的特性包,比如 linux 下的 `deb` 和 `rpm`,window 平台下的 `msi` 和 `exe`
|
||||
|
||||
## Java 15
|
||||
|
||||
JDK 15 于 2020 年 9 月 15 日发布,这是一个非 LTS 版本。
|
||||
|
||||
这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||||
|
||||
- [JEP 379: Shenandoah: A Low-Pause-Time Garbage Collector (Shenandoah GC, 转正)](https://openjdk.org/jeps/379)
|
||||
- [JEP 371: Hidden Classes (隐藏类)](https://openjdk.org/jeps/371)
|
||||
- [JEP 378: Text Blocks (Standard) (文本块, 转正)](https://openjdk.org/jeps/378)
|
||||
- [JEP 360: Sealed Classes (Preview) (密封类, 预览特性)](https://openjdk.org/jeps/360)
|
||||
- [JEP 339: EdDSA (数字签名算法)](https://openjdk.org/jeps/339)
|
||||
|
||||
下图是从 JDK 8 到 JDK 24 每个版本的更新带来新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
### JEP 379: Shenandoah(Shenandoah GC,标准版)
|
||||
|
||||
Shenandoah 垃圾收集器从 Java 12 开始作为实验性特性引入,经过多个版本的迭代和完善,在 Java 15 终于转正为正式特性。
|
||||
|
||||
Shenandoah 是 Red Hat 主导开发的低延迟垃圾收集器,主要目标是:
|
||||
|
||||
- 99.9% 的 GC 停顿时间小于 10ms
|
||||
- 停顿时间与堆大小无关
|
||||
- 支持从几百 MB 到几 TB 的堆内存
|
||||
|
||||
与 G1 和 ZGC 不同,Shenandoah 采用并发标记-整理算法,可以在不停止应用线程的情况下进行垃圾回收。
|
||||
|
||||
启用 Shenandoah GC:
|
||||
|
||||
```bash
|
||||
java -XX:+UseShenandoahGC className
|
||||
```
|
||||
|
||||
### JEP 339: EdDSA(数字签名算法)
|
||||
|
||||
新加入了一个安全性和性能都更强的基于 Edwards-Curve Digital Signature Algorithm(EdDSA)实现的数字签名算法。
|
||||
|
||||
虽然其性能优于现有的 ECDSA 实现,不过,它并不会完全取代 JDK 中现有的椭圆曲线数字签名算法( ECDSA)。
|
||||
|
||||
```java
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519");
|
||||
KeyPair kp = kpg.generateKeyPair();
|
||||
|
||||
byte[] msg = "test_string".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
Signature sig = Signature.getInstance("Ed25519");
|
||||
sig.initSign(kp.getPrivate());
|
||||
sig.update(msg);
|
||||
byte[] s = sig.sign();
|
||||
|
||||
String encodedString = Base64.getEncoder().encodeToString(s);
|
||||
System.out.println(encodedString);
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
```plain
|
||||
0Hc0lxxASZNvS52WsvnncJOH/mlFhnA8Tc6D/k5DtAX5BSsNVjtPF4R4+yMWXVjrvB2mxVXmChIbki6goFBgAg==
|
||||
```
|
||||
|
||||
### JEP 378: Text Blocks(文本块,标准版)
|
||||
|
||||
在 Java 15,文本块终于转正为正式功能特性,不再需要 `--enable-preview` 参数即可使用。
|
||||
|
||||
文本块提供了一种更简洁的方式来定义多行字符串,特别适合用于 SQL、JSON、HTML 等场景。
|
||||
|
||||
### JEP 371: Hidden Classes(隐藏类)
|
||||
|
||||
隐藏类是为框架(frameworks)所设计的,支持动态生成的类,这些类不能直接被其他类的字节码使用,只能在运行时通过反射间接使用它们。
|
||||
|
||||
主要特点:
|
||||
|
||||
- 不可被发现:隐藏类不能被其他类直接依赖
|
||||
- 生命周期短:通常在使用完毕后就会被卸载
|
||||
- 性能优化:为动态语言运行时提供了更好的性能支持
|
||||
|
||||
适用场景:
|
||||
|
||||
- 动态语言运行时(如 JavaScript、Groovy)
|
||||
- 字节码生成框架(如 ASM、CGLIB)
|
||||
- 反射代理(如动态代理)
|
||||
|
||||
### JEP 360: Sealed Classes(密封类,预览)
|
||||
|
||||
**密封类(Sealed Classes)** 是 Java 15 中的一个预览新特性。
|
||||
|
||||
没有密封类之前,在 Java 中如果想让一个类不能被继承和修改,我们可以使用 `final` 关键字对类进行修饰。不过,这种方式不太灵活,直接把一个类的继承和修改渠道给堵死了。
|
||||
|
||||
密封类可以对继承或者实现它们的类进行限制,这样这个类就只能被指定的类继承。
|
||||
|
||||
```java
|
||||
// 抽象类 Person 只允许 Employee 和 Manager 继承。
|
||||
public abstract sealed class Person
|
||||
permits Employee, Manager {
|
||||
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
另外,任何扩展密封类的类本身都必须声明为 `sealed`、`non-sealed` 或 `final`。
|
||||
|
||||
```java
|
||||
public final class Employee extends Person {
|
||||
}
|
||||
|
||||
public non-sealed class Manager extends Person {
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
如果允许扩展的子类和封闭类在同一个源代码文件里,封闭类可以不使用 permits 语句,Java 编译器将检索源文件,在编译期为封闭类添加上许可的子类。
|
||||
|
||||
### JEP 375: Pattern Matching for instanceof(instanceof 模式匹配,第二次预览)
|
||||
|
||||
Java 15 继续将 instanceof 模式匹配作为预览特性,没有进行重大调整,主要是为了收集更多使用反馈。
|
||||
|
||||
instanceof 模式匹配允许在类型检查的同时进行变量绑定,避免了显式类型转换,使代码更简洁安全。
|
||||
|
||||
示例:
|
||||
|
||||
```java
|
||||
// 传统写法
|
||||
if (obj instanceof String) {
|
||||
String str = (String) obj;
|
||||
System.out.println(str.length());
|
||||
}
|
||||
|
||||
// 模式匹配写法
|
||||
if (obj instanceof String str) {
|
||||
System.out.println(str.length());
|
||||
}
|
||||
```
|
||||
|
||||
### API 增强
|
||||
|
||||
#### CharSequence 增强
|
||||
|
||||
在 Java 15 之前,如果你想判断一个 `StringBuilder`、`StringBuffer` 或者 `CharBuffer` 是否为空,通常需要调用 `length() == 0`。
|
||||
|
||||
`CharSequence` 接口添加了一个默认方法 `isEmpty()` 来判断字符序列为空,如果是则返回 true。
|
||||
|
||||
```java
|
||||
public interface CharSequence {
|
||||
default boolean isEmpty() {
|
||||
return this.length() == 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
由于 `String`、`StringBuilder` 等都实现了这个接口,现在你可以用更统一、更具语义化的方式来编写判断逻辑。这对于编写泛型代码(处理各种字符序列)非常友好。
|
||||
|
||||
**注意:** `String` 类虽然早已有了 `isEmpty()` 方法,但那个是 `String` 自己定义的;Java 15 这一步是将该能力“上浮”到了接口层。
|
||||
|
||||
#### TreeMap 性能提升
|
||||
|
||||
这是一个非常硬核的优化。虽然 `putIfAbsent()`、`compute()` 等方法早在 Java 8 就出现在 `Map` 接口中了,但在 Java 15 之前,`TreeMap` 并没有自己去实现它们,而是直接沿用接口里的 `default` 实现。
|
||||
|
||||
**Java 15 的变化**:`TreeMap` 专门重写了以下方法:
|
||||
|
||||
- `putIfAbsent()`
|
||||
- `computeIfAbsent()`
|
||||
- `computeIfPresent()`
|
||||
- `compute()`
|
||||
- `merge()`
|
||||
|
||||
**为什么要重写?**
|
||||
|
||||
- **性能提升**:接口里的默认实现通常比较“笨”,往往需要进行多次查找(例如先查找是否存在,再决定是否插入)。
|
||||
- **O(log n) 保证**:`TreeMap` 优化后的实现可以将查找和插入合并在一次红黑树遍历中完成,避免了重复的树搜索,显著提升了在处理大规模数据时的执行效率。
|
||||
|
||||
### 其他特性
|
||||
|
||||
- **Nashorn JavaScript 引擎彻底移除**:Nashorn 从 Java8 开始引入的 JavaScript 引擎,Java9 对 Nashorn 做了些增强,实现了一些 ES6 的新特性。在 Java 11 中就已经被弃用,到了 Java 15 就彻底被删除了。
|
||||
- **DatagramSocket API 重构**
|
||||
- **禁用和废弃偏向锁(Biased Locking)**:偏向锁的引入增加了 JVM 的复杂性大于其带来的性能提升。不过,你仍然可以使用 `-XX:+UseBiasedLocking` 启用偏向锁定,但它会提示这是一个已弃用的 API。
|
||||
- ……
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
@@ -0,0 +1,172 @@
|
||||
---
|
||||
title: Java 16 新特性概览
|
||||
description: 介绍 JDK 16 的语言与平台更新,包含记录类与其他 JEP 改动。
|
||||
category: Java
|
||||
tag:
|
||||
- Java新特性
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java 16,JDK16,记录类改进,新 API,JEP,性能
|
||||
---
|
||||
|
||||
Java 16 在 2021 年 3 月 16 日正式发布,非长期支持(LTS)版本。
|
||||
|
||||
JDK 16 共有 17 个新特性,这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||||
|
||||
- [JEP 338: Vector API (Incubator)(向量 API,第一次孵化)](https://openjdk.java.net/jeps/338)
|
||||
- [JEP 376: ZGC: Concurrent Thread-Stack Processing(ZGC 并发线程栈处理)](https://openjdk.java.net/jeps/376)
|
||||
- [JEP 387: Elastic Metaspace(弹性元空间)](https://openjdk.java.net/jeps/387)
|
||||
- [JEP 390: Warnings for Value-Based Classes(基于值的类的警告)](https://openjdk.java.net/jeps/390)
|
||||
- [JEP 394: Pattern Matching for instanceof(instanceof 模式匹配,转正)](https://openjdk.java.net/jeps/394)
|
||||
- [JEP 395: Records(record 类,转正)](https://openjdk.java.net/jeps/395)
|
||||
- [JEP 396: Strongly Encapsulate JDK Internals by Default(默认强封装 JDK 内部元素)](https://openjdk.java.net/jeps/396)
|
||||
- [JEP 397: Sealed Classes (Second Preview)(密封类,第二次预览)](https://openjdk.java.net/jeps/397)
|
||||
|
||||
下图是从 JDK 8 到 JDK 25 每个版本的更新带来的新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
相关阅读:[OpenJDK Java 16 文档](https://openjdk.java.net/projects/jdk/16/)。
|
||||
|
||||
## JEP 338: Vector API(向量 API,第一次孵化)
|
||||
|
||||
向量(Vector) API 最初由 [JEP 338](https://openjdk.java.net/jeps/338) 提出,并作为[孵化 API](http://openjdk.java.net/jeps/11)集成到 Java 16 中。第二轮孵化由 [JEP 414](https://openjdk.java.net/jeps/414) 提出并集成到 Java 17 中,第三轮孵化由 [JEP 417](https://openjdk.java.net/jeps/417) 提出并集成到 Java 18 中,第四轮由 [JEP 426](https://openjdk.java.net/jeps/426) 提出并集成到了 Java 19 中。
|
||||
|
||||
该孵化器 API 提供了一个 API 的初始迭代以表达一些向量计算,这些计算在运行时可靠地编译为支持的 CPU 架构上的最佳向量硬件指令,从而获得优于同等标量计算的性能,充分利用单指令多数据(SIMD)技术(大多数现代 CPU 上都可以使用的一种指令)。尽管 HotSpot 支持自动向量化,但是可转换的标量操作集有限且易受代码更改的影响。该 API 将使开发人员能够轻松地用 Java 编写可移植的高性能向量算法。
|
||||
|
||||
在 [Java 18 新特性概览](./java18.md) 中,我有详细介绍到向量 API,这里就不再做额外的介绍了。
|
||||
|
||||
## JEP 347: Enable C++ 14 Language Features(启用 C++ 14 语言特性)
|
||||
|
||||
Java 16 允许在 JDK 的 C++ 源代码中使用 C++ 14 语言特性,并提供在 HotSpot 代码中可以使用哪些特性的具体指导。
|
||||
|
||||
在 Java 15 中,JDK 中 C++ 代码使用的语言特性仅限于 C++98/03 语言标准。它要求更新各种平台编译器的最低可接受版本。
|
||||
|
||||
## JEP 376: ZGC: Concurrent Thread-Stack Processing(ZGC 并发线程栈处理)
|
||||
|
||||
Java16 将 ZGC 线程栈处理从安全点转移到一个并发阶段,甚至在大堆上也允许在毫秒内暂停 GC 安全点。消除 ZGC 垃圾收集器中最后一个延迟源可以极大地提高应用程序的性能和效率。
|
||||
|
||||
## JEP 387: Elastic Metaspace(弹性元空间)
|
||||
|
||||
自从引入了 Metaspace 以来,根据反馈,Metaspace 经常占用过多的堆外内存,从而导致内存浪费。弹性元空间这个特性可将未使用的 HotSpot 类元数据(即元空间,metaspace)内存更快速地返回到操作系统,从而减少元空间的占用空间。
|
||||
|
||||
并且,这个提案还简化了元空间的代码以降低维护成本。
|
||||
|
||||
## JEP 390: Warnings for Value-Based Classes(基于值的类的警告)
|
||||
|
||||
> 以下介绍摘自:[实操 | 剖析 Java16 新语法特性](https://xie.infoq.cn/article/8304c894c4e38318d38ceb116),原文写的很不错,推荐阅读。
|
||||
|
||||
早在 Java9 版本时,Java 的设计者们就对 `@Deprecated` 注解进行了一次升级,增加了 `since` 和 `forRemoval` 等 2 个新元素。其中,since 元素用于指定标记了 `@Deprecated` 注解的 API 被弃用时的版本,而 `forRemoval` 则进一步明确了 API 标记 @Deprecated 注解时的语义,如果 `forRemoval=true` 时,则表示该 API 在未来版本中肯定会被删除,开发人员应该使用新的 API 进行替代,不再容易产生歧义(Java9 之前,标记 @Deprecated 注解的 API,语义上存在多种可能性,比如:存在使用风险、可能在未来存在兼容性错误、可能在未来版本中被删除,以及应该使用更好的替代方案等)。
|
||||
|
||||
仔细观察原始类型的包装类(比如:`java.lang.Integer`、`java.lang.Double`),不难发现,其构造函数上都已经标记有 `@Deprecated(since="9", forRemoval = true)` 注解,这就意味着其构造函数在将来会被删除,不应该在程序中继续使用诸如 `new Integer();` 这样的编码方式(建议使用 `Integer a = 10;` 或者 `Integer.valueOf()` 函数),如果继续使用,编译期将会产生'Integer(int)' is deprecated and marked for removal 告警。并且,值得注意的是,这些包装类型已经被指定为同 `java.util.Optional` 和 `java.time.LocalDateTime` 一样的值类型。
|
||||
|
||||
其次,如果继续在 `synchronized` 同步块中使用值类型,将会在编译期和运行期产生警告,甚至是异常。在此大家需要注意,就算编译期和运行期没有产生警告和异常,也不建议在 `synchronized` 同步块中使用值类型,举个自增的例子。示例 1-5:
|
||||
|
||||
```java
|
||||
public void inc(Integer count) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
new Thread(() -> {
|
||||
synchronized (count) {
|
||||
count++;
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
当执行上述程序示例时,最终的输出结果一定会与你的期望产生差异,这是许多新人经常犯错的一个点,因为在并发环境下,`Integer` 对象根本无法通过 `synchronized` 来保证线程安全,这是因为每次的 `count++` 操作,所产生的 `hashcode` 均不同,简而言之,每次加锁都锁在了不同的对象上。因此,如果希望在实际的开发过程中保证其原子性,应该使用 `AtomicInteger`。
|
||||
|
||||
## JEP 392: Packaging Tool(打包工具,转正)
|
||||
|
||||
在 Java 14 中,JEP 343 引入了打包工具,命令是 `jpackage`。在 Java 15 中,继续孵化,现在在 Java 16 中,终于成为了正式功能。
|
||||
|
||||
这个打包工具允许打包自包含的 Java 应用程序。它支持原生打包格式,为最终用户提供自然的安装体验,这些格式包括 Windows 上的 msi 和 exe、macOS 上的 pkg 和 dmg,还有 Linux 上的 deb 和 rpm。它还允许在打包时指定启动时参数,并且可以从命令行直接调用,也可以通过 ToolProvider API 以编程方式调用。注意 jpackage 模块名称从 jdk.incubator.jpackage 更改为 jdk.jpackage。这将改善最终用户在安装应用程序时的体验,并简化了“应用商店”模型的部署。
|
||||
|
||||
关于这个打包工具的实际使用,可以看这个视频 [Playing with Java 16 jpackage](https://www.youtube.com/watch?v=KahYIVzRIkQ)(需要梯子)。
|
||||
|
||||
## JEP 393: Foreign Memory Access API(外部内存访问 API,第三次孵化)
|
||||
|
||||
引入外部内存访问 API 以允许 Java 程序安全有效地访问 Java 堆之外的外部内存。
|
||||
|
||||
Java 14([JEP 370](https://openjdk.org/jeps/370)) 的时候,第一次孵化外部内存访问 API,Java 15 中进行了第二次复活([JEP 383](https://openjdk.org/jeps/383)),在 Java 16 中进行了第三次孵化。
|
||||
|
||||
引入外部内存访问 API 的目的如下:
|
||||
|
||||
- 通用:单个 API 应该能够对各种外部内存(如本机内存、持久内存、堆内存等)进行操作。
|
||||
- 安全:无论操作何种内存,API 都不应该破坏 JVM 的安全性。
|
||||
- 控制:可以自由的选择如何释放内存(显式、隐式等)。
|
||||
- 可用:如果需要访问外部内存,API 应该是 `sun.misc.Unsafe`.
|
||||
|
||||
## JEP 394: Pattern Matching for instanceof(instanceof 模式匹配,转正)
|
||||
|
||||
| JDK 版本 | 更新类型 | JEP | 更新内容 |
|
||||
| ---------- | ----------------- | --------------------------------------- | ---------------------------------------- |
|
||||
| Java SE 14 | preview | [JEP 305](https://openjdk.org/jeps/305) | 首次引入 instanceof 模式匹配。 |
|
||||
| Java SE 15 | Second Preview | [JEP 375](https://openjdk.org/jeps/375) | 相比较上个版本无变化,继续收集更多反馈。 |
|
||||
| Java SE 16 | Permanent Release | [JEP 394](https://openjdk.org/jeps/394) | 模式变量不再隐式为 final。 |
|
||||
|
||||
从 Java 16 开始,你可以对 `instanceof` 中的变量值进行修改。
|
||||
|
||||
```java
|
||||
// Old code
|
||||
if (o instanceof String) {
|
||||
String s = (String)o;
|
||||
... use s ...
|
||||
}
|
||||
|
||||
// New code
|
||||
if (o instanceof String s) {
|
||||
... use s ...
|
||||
}
|
||||
```
|
||||
|
||||
## JEP 395: Records(record 类,转正)
|
||||
|
||||
记录类型变更历史:
|
||||
|
||||
| JDK 版本 | 更新类型 | JEP | 更新内容 |
|
||||
| ---------- | ----------------- | -------------------------------------------- | ------------------------------------------------------------------------- |
|
||||
| Java SE 14 | Preview | [JEP 359](https://openjdk.java.net/jeps/359) | 引入 `record` 关键字,`record` 提供一种紧凑的语法来定义类中的不可变数据。 |
|
||||
| Java SE 15 | Second Preview | [JEP 384](https://openjdk.org/jeps/384) | 支持在局部方法和接口中使用 `record`。 |
|
||||
| Java SE 16 | Permanent Release | [JEP 395](https://openjdk.org/jeps/395) | 非静态内部类可以定义非常量的静态成员。 |
|
||||
|
||||
从 Java SE 16 开始,非静态内部类可以定义非常量的静态成员。
|
||||
|
||||
```java
|
||||
public class Outer {
|
||||
class Inner {
|
||||
static int age;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 在 JDK 16 之前,如果写上面这种代码,IDE 会提示你静态字段 age 不能在非静态的内部类中定义,除非它用一个常量表达式初始化。(The field age cannot be declared static in a non-static inner type, unless initialized with a constant expression)
|
||||
|
||||
## JEP 396: Strongly Encapsulate JDK Internals by Default(默认强封装 JDK 内部元素)
|
||||
|
||||
此特性会默认强封装 JDK 的所有内部元素,但关键内部 API(例如 `sun.misc.Unsafe`)除外。默认情况下,使用早期版本成功编译的访问 JDK 内部 API 的代码可能不再起作用。鼓励开发人员从使用内部元素迁移到使用标准 API 的方法上,以便他们及其用户都可以无缝升级到将来的 Java 版本。强封装由 JDK 9 的启动器选项–illegal-access 控制,到 JDK 15 默认改为 warning,从 JDK 16 开始默认为 deny。(目前)仍然可以使用单个命令行选项放宽对所有软件包的封装,将来只有使用–add-opens 打开特定的软件包才行。
|
||||
|
||||
## JEP 397: Sealed Classes(密封类,第二次预览)
|
||||
|
||||
密封类由 [JEP 360](https://openjdk.java.net/jeps/360) 提出预览,集成到了 Java 15 中。在 JDK 16 中, 密封类得到了改进(更加严格的引用检查和密封类的继承关系),由 [JEP 397](https://openjdk.java.net/jeps/397) 提出了再次预览。
|
||||
|
||||
在 [Java 14 & 15 新特性概览](./java14-15.md) 中,我有详细介绍到密封类,这里就不再做额外的介绍了。
|
||||
|
||||
## 其他优化与改进
|
||||
|
||||
- **JEP 380:Unix-Domain 套接字通道**:Unix-domain 套接字一直是大多数 Unix 平台的一个特性,现在在 Windows 10 和 Windows Server 2019 也提供了支持。此特性为 java.nio.channels 包的套接字通道和服务器套接字通道 API 添加了 Unix-domain(AF_UNIX)套接字支持。它扩展了继承的通道机制以支持 Unix-domain 套接字通道和服务器套接字通道。Unix-domain 套接字用于同一主机上的进程间通信(IPC)。它们在很大程度上类似于 TCP/IP,区别在于套接字是通过文件系统路径名而不是 Internet 协议(IP)地址和端口号寻址的。对于本地进程间通信,Unix-domain 套接字比 TCP/IP 环回连接更安全、更有效
|
||||
- **JEP 389:外部链接器 API(孵化):** 该孵化器 API 提供了静态类型、纯 Java 访问原生代码的特性,该 API 将大大简化绑定原生库的原本复杂且容易出错的过程。Java 1.1 就已通过 Java 原生接口(JNI)支持了原生方法调用,但并不好用。Java 开发人员应该能够为特定任务绑定特定的原生库。它还提供了外来函数支持,而无需任何中间的 JNI 粘合代码。
|
||||
- **JEP 357:从 Mercurial 迁移到 Git**:在此之前,OpenJDK 源代码是使用版本管理工具 Mercurial 进行管理,现在迁移到了 Git。
|
||||
- **JEP 369:迁移到 GitHub**:和 JEP 357 从 Mercurial 迁移到 Git 的改变一致,在把版本管理迁移到 Git 之后,选择了在 GitHub 上托管 OpenJDK 社区的 Git 仓库。不过只对 JDK 11 以及更高版本 JDK 进行了迁移。
|
||||
- **JEP 386:移植 Alpine Linux**:Alpine Linux 是一个独立的、非商业的 Linux 发行版,它十分的小,一个容器需要不超过 8MB 的空间,最小安装到磁盘只需要大约 130MB 存储空间,并且十分的简单,同时兼顾了安全性。此提案将 JDK 移植到了 Apline Linux,由于 Apline Linux 是基于 musl lib 的轻量级 Linux 发行版,因此其他 x64 和 AArch64 架构上使用 musl lib 的 Linux 发行版也适用。
|
||||
- **JEP 388:Windows/AArch64 移植**:这些 JEP 的重点不是移植工作本身,而是将它们集成到 JDK 主线存储库中;JEP 386 将 JDK 移植到 Alpine Linux 和其他使用 musl 作为 x64 上主要 C 库的发行版上。此外,JEP 388 将 JDK 移植到 Windows AArch64(ARM64)。
|
||||
|
||||
## 参考文献
|
||||
|
||||
- [Java Language Changes](https://docs.oracle.com/en/java/javase/16/language/java-language-changes.html)
|
||||
- [Consolidated JDK 16 Release Notes](https://www.oracle.com/java/technologies/javase/16all-relnotes.html)
|
||||
- [Java 16 正式发布,新特性一一解析](https://www.infoq.cn/article/IAkwhx7i9V7G8zLVEd4L)
|
||||
- [实操 | 剖析 Java16 新语法特性](https://xie.infoq.cn/article/8304c894c4e38318d38ceb116)(写的很赞)
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
@@ -0,0 +1,176 @@
|
||||
---
|
||||
title: Java 17 新特性概览(重要)
|
||||
description: 总结 JDK 17 的重要更新与 JEP,涵盖密封类、记录类与模式匹配等特性。
|
||||
category: Java
|
||||
tag:
|
||||
- Java新特性
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java 17,JDK17,LTS,密封类,记录类,模式匹配,API 更新,JEP
|
||||
---
|
||||
|
||||
Java 17 在 2021 年 9 月 14 日正式发布,是一个长期支持(LTS)版本。
|
||||
|
||||
下面这张图是 Oracle 官方给出的 Oracle JDK 支持的时间线。可以看得到,Java 17 最多可以支持到 2029 年 9 月份。
|
||||
|
||||

|
||||
|
||||
Java 17 将是继 Java 8 以来最重要的长期支持(LTS)版本,是 Java 社区八年努力的成果。Spring 6.x 和 Spring Boot 3.x 最低支持的就是 Java 17。
|
||||
|
||||
JDK 17 共有 14 个新特性,这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||||
|
||||
- [JEP 356: Enhanced Pseudo-Random Number Generators(增强的伪随机数生成器)](https://openjdk.java.net/jeps/356)
|
||||
- [JEP 398: Deprecate the Applet API for Removal(标记弃用 Applet API 以便移除)](https://openjdk.java.net/jeps/398)
|
||||
- [JEP 406: Pattern Matching for switch (Preview)(switch 模式匹配,预览)](https://openjdk.java.net/jeps/406)
|
||||
- [JEP 407: Remove RMI Activation(移除 RMI 激活机制)](https://openjdk.java.net/jeps/407)
|
||||
- [JEP 409: Sealed Classes(密封类,转正)](https://openjdk.java.net/jeps/409)
|
||||
- [JEP 410: Remove the Experimental AOT and JIT Compiler(移除实验性的 AOT 和 JIT 编译器)](https://openjdk.java.net/jeps/410)
|
||||
- [JEP 411: Deprecate the Security Manager for Removal(标记弃用安全管理器以便移除)](https://openjdk.java.net/jeps/411)
|
||||
- [JEP 412: Foreign Function & Memory API (Incubator)(外部函数和内存 API,第一次孵化)](https://openjdk.java.net/jeps/412)
|
||||
- [JEP 414: Vector API (Second Incubator)(向量 API,第二次孵化)](https://openjdk.java.net/jeps/414)
|
||||
|
||||
下图是从 JDK 8 到 JDK 16 每个版本的更新带来的新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
相关阅读:[OpenJDK Java 17 文档](https://openjdk.java.net/projects/jdk/17/)。
|
||||
|
||||
## JEP 356: Enhanced Pseudo-Random Number Generators(增强的伪随机数生成器)
|
||||
|
||||
JDK 17 之前,我们可以借助 `Random`、`ThreadLocalRandom` 和 `SplittableRandom` 来生成随机数。不过,这 3 个类都各有缺陷,且缺少常见的伪随机算法支持。
|
||||
|
||||
Java 17 为伪随机数生成器(pseudorandom number generator,PRNG,又称为确定性随机位生成器)增加了新的接口类型和实现,使得开发者更容易在应用程序中互换使用各种 PRNG 算法。
|
||||
|
||||
> [PRNG](https://ctf-wiki.org/crypto/streamcipher/prng/intro/) 用来生成接近于绝对随机数序列的数字序列。一般来说,PRNG 会依赖于一个初始值,也称为种子,来生成对应的伪随机数序列。只要种子确定了,PRNG 所生成的随机数就是完全确定的,因此其生成的随机数序列并不是真正随机的。
|
||||
|
||||
使用示例:
|
||||
|
||||
```java
|
||||
RandomGeneratorFactory<RandomGenerator> l128X256MixRandom = RandomGeneratorFactory.of("L128X256MixRandom");
|
||||
// 使用时间戳作为随机数种子
|
||||
RandomGenerator randomGenerator = l128X256MixRandom.create(System.currentTimeMillis());
|
||||
// 生成随机数
|
||||
randomGenerator.nextInt(10);
|
||||
```
|
||||
|
||||
## JEP 398: Deprecate the Applet API for Removal(标记弃用 Applet API 以便移除)
|
||||
|
||||
Applet API 用于编写在 Web 浏览器端运行的 Java 小程序,很多年前就已经被淘汰了,已经没有理由使用了。
|
||||
|
||||
Applet API 在 Java 9 时被标记弃用([JEP 289](https://openjdk.java.net/jeps/289)),但不是为了删除。
|
||||
|
||||
## JEP 406: Pattern Matching for switch(switch 模式匹配,预览)
|
||||
|
||||
正如 `instanceof` 一样, `switch` 也紧跟着增加了类型匹配自动转换功能。
|
||||
|
||||
`instanceof` 代码示例:
|
||||
|
||||
```java
|
||||
// Old code
|
||||
if (o instanceof String) {
|
||||
String s = (String)o;
|
||||
... use s ...
|
||||
}
|
||||
|
||||
// New code
|
||||
if (o instanceof String s) {
|
||||
... use s ...
|
||||
}
|
||||
```
|
||||
|
||||
`switch` 代码示例:
|
||||
|
||||
```java
|
||||
// Old code
|
||||
static String formatter(Object o) {
|
||||
String formatted = "unknown";
|
||||
if (o instanceof Integer i) {
|
||||
formatted = String.format("int %d", i);
|
||||
} else if (o instanceof Long l) {
|
||||
formatted = String.format("long %d", l);
|
||||
} else if (o instanceof Double d) {
|
||||
formatted = String.format("double %f", d);
|
||||
} else if (o instanceof String s) {
|
||||
formatted = String.format("String %s", s);
|
||||
}
|
||||
return formatted;
|
||||
}
|
||||
|
||||
// New code
|
||||
static String formatterPatternSwitch(Object o) {
|
||||
return switch (o) {
|
||||
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 -> o.toString();
|
||||
};
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
对于 `null` 值的判断也进行了优化。
|
||||
|
||||
```java
|
||||
// Old code
|
||||
static void testFooBar(String s) {
|
||||
if (s == null) {
|
||||
System.out.println("oops!");
|
||||
return;
|
||||
}
|
||||
switch (s) {
|
||||
case "Foo", "Bar" -> System.out.println("Great");
|
||||
default -> System.out.println("Ok");
|
||||
}
|
||||
}
|
||||
|
||||
// New code
|
||||
static void testFooBar(String s) {
|
||||
switch (s) {
|
||||
case null -> System.out.println("Oops");
|
||||
case "Foo", "Bar" -> System.out.println("Great");
|
||||
default -> System.out.println("Ok");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## JEP 407: Remove RMI Activation(移除 RMI 激活机制)
|
||||
|
||||
删除远程方法调用 (RMI) 激活机制,同时保留 RMI 的其余部分。RMI 激活机制已过时且不再使用。
|
||||
|
||||
## JEP 409: Sealed Classes(密封类)
|
||||
|
||||
密封类由 [JEP 360](https://openjdk.java.net/jeps/360) 提出预览,集成到了 Java 15 中。在 JDK 16 中, 密封类得到了改进(更加严格的引用检查和密封类的继承关系),由 [JEP 397](https://openjdk.java.net/jeps/397) 提出了再次预览。
|
||||
|
||||
在 [Java 14 & 15 新特性概览](./java14-15.md) 中,我有详细介绍到密封类,这里就不再做额外的介绍了。
|
||||
|
||||
## JEP 410: Remove the Experimental AOT and JIT Compiler(移除实验性的 AOT 和 JIT 编译器)
|
||||
|
||||
在 Java 9 的 [JEP 295](https://openjdk.java.net/jeps/295) ,引入了实验性的提前 (AOT) 编译器,在启动虚拟机之前将 Java 类编译为本机代码。
|
||||
|
||||
Java 17,删除实验性的提前 (AOT) 和即时 (JIT) 编译器,因为该编译器自推出以来很少使用,维护它所需的工作量很大。保留实验性的 Java 级 JVM 编译器接口 (JVMCI),以便开发人员可以继续使用外部构建的编译器版本进行 JIT 编译。
|
||||
|
||||
## JEP 411: Deprecate the Security Manager for Removal(标记弃用安全管理器以便移除)
|
||||
|
||||
弃用安全管理器以便在将来的版本中删除。
|
||||
|
||||
安全管理器可追溯到 Java 1.0,多年来,它一直不是保护客户端 Java 代码的主要方法,也很少用于保护服务器端代码。为了推动 Java 向前发展,Java 17 弃用安全管理器,以便与旧版 Applet API ( [JEP 398](https://openjdk.java.net/jeps/398) ) 一起移除。
|
||||
|
||||
## JEP 412: Foreign Function & Memory API(外部函数和内存 API,孵化)
|
||||
|
||||
Java 程序可以通过该 API 与 Java 运行时之外的代码和数据进行互操作。通过高效地调用外部函数(即 JVM 之外的代码)和安全地访问外部内存(即不受 JVM 管理的内存),该 API 使 Java 程序能够调用本机库并处理本机数据,而不会像 JNI 那样危险和脆弱。
|
||||
|
||||
外部函数和内存 API 在 Java 17 中进行了第一轮孵化,由 [JEP 412](https://openjdk.java.net/jeps/412) 提出。第二轮孵化由[JEP 419](https://openjdk.org/jeps/419) 提出并集成到了 Java 18 中,预览由 [JEP 424](https://openjdk.org/jeps/424) 提出并集成到了 Java 19 中。
|
||||
|
||||
在 [Java 19 新特性概览](./java19.md) 中,我有详细介绍到外部函数和内存 API,这里就不再做额外的介绍了。
|
||||
|
||||
## JEP 414: Vector API(向量 API,第二次孵化)
|
||||
|
||||
向量(Vector) API 最初由 [JEP 338](https://openjdk.java.net/jeps/338) 提出,并作为[孵化 API](http://openjdk.java.net/jeps/11)集成到 Java 16 中。第二轮孵化由 [JEP 414](https://openjdk.java.net/jeps/414) 提出并集成到 Java 17 中,第三轮孵化由 [JEP 417](https://openjdk.java.net/jeps/417) 提出并集成到 Java 18 中,第四轮由 [JEP 426](https://openjdk.java.net/jeps/426) 提出并集成到了 Java 19 中。
|
||||
|
||||
该孵化器 API 提供了一个 API 的初始迭代以表达一些向量计算,这些计算在运行时可靠地编译为支持的 CPU 架构上的最佳向量硬件指令,从而获得优于同等标量计算的性能,充分利用单指令多数据(SIMD)技术(大多数现代 CPU 上都可以使用的一种指令)。尽管 HotSpot 支持自动向量化,但是可转换的标量操作集有限且易受代码更改的影响。该 API 将使开发人员能够轻松地用 Java 编写可移植的高性能向量算法。
|
||||
|
||||
在 [Java 18 新特性概览](./java18.md) 中,我有详细介绍到向量 API,这里就不再做额外的介绍了。
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
@@ -0,0 +1,144 @@
|
||||
---
|
||||
title: Java 18 新特性概览
|
||||
description: 概览 JDK 18 的更新与预览特性,理解新 API 带来的改进。
|
||||
category: Java
|
||||
tag:
|
||||
- Java新特性
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java 18,JDK18,预览特性,API 更新,JEP
|
||||
---
|
||||
|
||||
Java 18 在 2022 年 3 月 22 日正式发布,非长期支持版本。
|
||||
|
||||
JDK 18 共有 8 个新特性,这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||||
|
||||
- [JEP 400: UTF-8 by Default(UTF-8 作为默认字符集)](https://openjdk.java.net/jeps/400)
|
||||
- [JEP 408: Simple Web Server(简单 Web 服务器)](https://openjdk.java.net/jeps/408)
|
||||
- [JEP 413: Code Snippets in Java API Documentation(API 文档代码片段)](https://openjdk.java.net/jeps/413)
|
||||
- [JEP 416: Reimplement Core Reflection with Method Handles(方法句柄重构核心反射)](https://openjdk.java.net/jeps/416)
|
||||
- [JEP 417: Vector API (Third Incubator)(向量 API,第三次孵化)](https://openjdk.java.net/jeps/417)
|
||||
- [JEP 418: Internet-Address Resolution SPI(互联网地址解析 SPI)](https://openjdk.java.net/jeps/418)
|
||||
- [JEP 419: Foreign Function & Memory API (Second Incubator)(外部函数和内存 API,第二次孵化)](https://openjdk.java.net/jeps/419)
|
||||
|
||||
下图是从 JDK 8 到 JDK 25 每个版本的更新带来的新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
相关阅读:
|
||||
|
||||
- [OpenJDK Java 18 文档](https://openjdk.java.net/projects/jdk/18/)
|
||||
- [IntelliJ IDEA | Java 18 功能支持](https://mp.weixin.qq.com/s/PocFKR9z9u7-YCZHsrA5kQ)
|
||||
|
||||
## JEP 400: UTF-8 by Default(UTF-8 作为默认字符集,转正)
|
||||
|
||||
JDK 终于将 UTF-8 设置为默认字符集。
|
||||
|
||||
在 Java 17 及更早版本中,默认字符集是在 Java 虚拟机运行时才确定的,取决于不同的操作系统、区域设置等因素,因此存在潜在的风险。就比如说你在 Mac 上运行正常的一段打印文字到控制台的 Java 程序到了 Windows 上就会出现乱码,如果你不手动更改字符集的话。
|
||||
|
||||
## JEP 408: Simple Web Server(简单 Web 服务器,转正)
|
||||
|
||||
Java 18 之后,你可以使用 `jwebserver` 命令启动一个简易的静态 Web 服务器。
|
||||
|
||||
```bash
|
||||
$ jwebserver
|
||||
Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
|
||||
Serving /cwd and subdirectories on 127.0.0.1 port 8000
|
||||
URL: http://127.0.0.1:8000/
|
||||
```
|
||||
|
||||
这个服务器不支持 CGI 和 Servlet,只限于静态文件。
|
||||
|
||||
## JEP 413: Code Snippets in Java API Documentation(API 文档代码片段,转正)
|
||||
|
||||
在 Java 18 之前,如果我们想要在 Javadoc 中引入代码片段可以使用 `<pre>{@code ...}</pre>`。
|
||||
|
||||
```java
|
||||
<pre>{@code
|
||||
lines of source code
|
||||
}</pre>
|
||||
```
|
||||
|
||||
`<pre>{@code ...}</pre>` 这种方式生成的效果比较一般。
|
||||
|
||||
在 Java 18 之后,可以通过 `@snippet` 标签来做这件事情。
|
||||
|
||||
```java
|
||||
/**
|
||||
* The following code shows how to use {@code Optional.isPresent}:
|
||||
* {@snippet :
|
||||
* if (v.isPresent()) {
|
||||
* System.out.println("v: " + v.get());
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
```
|
||||
|
||||
`@snippet` 这种方式生成的效果更好且使用起来更方便一些。
|
||||
|
||||
## JEP 416: Reimplement Core Reflection with Method Handles(方法句柄重构核心反射,转正)
|
||||
|
||||
Java 18 改进了 `java.lang.reflect.Method`、`Constructor` 的实现逻辑,使之性能更好,速度更快。这项改动不会改动相关 API,这意味着开发中不需要改动反射相关代码,就可以体验到性能更好的反射。
|
||||
|
||||
OpenJDK 官方给出了新老实现的反射性能基准测试结果。
|
||||
|
||||

|
||||
|
||||
## JEP 417: Vector API(向量 API,第三次孵化)
|
||||
|
||||
向量(Vector) API 最初由 [JEP 338](https://openjdk.java.net/jeps/338) 提出,并作为[孵化 API](http://openjdk.java.net/jeps/11)集成到 Java 16 中。第二轮孵化由 [JEP 414](https://openjdk.java.net/jeps/414) 提出并集成到 Java 17 中,第三轮孵化由 [JEP 417](https://openjdk.java.net/jeps/417) 提出并集成到 Java 18 中,第四轮由 [JEP 426](https://openjdk.java.net/jeps/426) 提出并集成到了 Java 19 中。
|
||||
|
||||
向量计算由对向量的一系列操作组成。向量 API 用来表达向量计算,该计算可以在运行时可靠地编译为支持的 CPU 架构上的最佳向量指令,从而实现优于等效标量计算的性能。
|
||||
|
||||
向量 API 的目标是为用户提供简洁易用且与平台无关的表达范围广泛的向量计算。
|
||||
|
||||
这是对数组元素的简单标量计算:
|
||||
|
||||
```java
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这是使用 Vector API 进行的等效向量计算:
|
||||
|
||||
```java
|
||||
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
|
||||
|
||||
void vectorComputation(float[] a, float[] b, float[] c) {
|
||||
int i = 0;
|
||||
int upperBound = SPECIES.loopBound(a.length);
|
||||
for (; i < upperBound; i += SPECIES.length()) {
|
||||
// FloatVector va, vb, vc;
|
||||
var va = FloatVector.fromArray(SPECIES, a, i);
|
||||
var vb = FloatVector.fromArray(SPECIES, b, i);
|
||||
var vc = va.mul(va)
|
||||
.add(vb.mul(vb))
|
||||
.neg();
|
||||
vc.intoArray(c, i);
|
||||
}
|
||||
for (; i < a.length; i++) {
|
||||
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
在 JDK 18 中,向量 API 的性能得到了进一步的优化。
|
||||
|
||||
## JEP 418: Internet-Address Resolution SPI(互联网地址解析 SPI,转正)
|
||||
|
||||
Java 18 定义了一个全新的 SPI(service-provider interface),用于主要名称和地址的解析,以便 `java.net.InetAddress` 可以使用平台之外的第三方解析器。
|
||||
|
||||
## JEP 419: Foreign Function & Memory API(外部函数和内存 API,第二次孵化)
|
||||
|
||||
Java 程序可以通过该 API 与 Java 运行时之外的代码和数据进行互操作。通过高效地调用外部函数(即 JVM 之外的代码)和安全地访问外部内存(即不受 JVM 管理的内存),该 API 使 Java 程序能够调用本机库并处理本机数据,而不会像 JNI 那样危险和脆弱。
|
||||
|
||||
外部函数和内存 API 在 Java 17 中进行了第一轮孵化,由 [JEP 412](https://openjdk.java.net/jeps/412) 提出。第二轮孵化由[JEP 419](https://openjdk.org/jeps/419) 提出并集成到了 Java 18 中,预览由 [JEP 424](https://openjdk.org/jeps/424) 提出并集成到了 Java 19 中。
|
||||
|
||||
在 [Java 19 新特性概览](./java19.md) 中,我有详细介绍到外部函数和内存 API,这里就不再做额外的介绍了。
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
title: Java 19 新特性概览
|
||||
description: 介绍 JDK 19 的预览特性与并发相关更新,为后续虚拟线程铺垫。
|
||||
category: Java
|
||||
tag:
|
||||
- Java新特性
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java 19,JDK19,虚拟线程预览,结构化并发,外部函数 API,JEP
|
||||
---
|
||||
|
||||
JDK 19 于 2022 年 9 月 20 日正式发布,非长期支持版本。
|
||||
|
||||
JDK 19 共有 7 个新特性,这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||||
|
||||
- [JEP 424: Foreign Function & Memory API(外部函数和内存 API)](https://openjdk.org/jeps/424)(预览)
|
||||
- [JEP 425: Virtual Threads(虚拟线程)](https://openjdk.org/jeps/425)(预览)
|
||||
- [JEP 426: Vector API(向量 API)](https://openjdk.java.net/jeps/426)(第四次孵化)
|
||||
- [JEP 428: Structured Concurrency(结构化并发)](https://openjdk.org/jeps/428)(孵化)
|
||||
|
||||
下图是从 JDK 8 到 JDK 25 每个版本的更新带来的新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
## JEP 424: 外部函数和内存 API(预览)
|
||||
|
||||
Java 程序可以通过该 API 与 Java 运行时之外的代码和数据进行互操作。通过高效地调用外部函数(即 JVM 之外的代码)和安全地访问外部内存(即不受 JVM 管理的内存),该 API 使 Java 程序能够调用本机库并处理本机数据,而不会像 JNI 那样危险和脆弱。
|
||||
|
||||
外部函数和内存 API 在 Java 17 中进行了第一轮孵化,由 [JEP 412](https://openjdk.java.net/jeps/412) 提出。第二轮孵化由[JEP 419](https://openjdk.org/jeps/419) 提出并集成到了 Java 18 中,预览由 [JEP 424](https://openjdk.org/jeps/424) 提出并集成到了 Java 19 中。
|
||||
|
||||
在没有外部函数和内存 API 之前:
|
||||
|
||||
- Java 通过 [`sun.misc.Unsafe`](https://hg.openjdk.java.net/jdk/jdk/file/tip/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java) 提供一些执行低级别、不安全操作的方法(如直接访问系统内存资源、自主管理内存资源等),`Unsafe` 类让 Java 语言拥有了类似 C 语言指针一样操作内存空间的能力的同时,也增加了 Java 语言的不安全性,不正确使用 `Unsafe` 类会使得程序出错的概率变大。
|
||||
- Java 1.1 就已通过 Java 原生接口(JNI)支持了原生方法调用,但并不好用。JNI 实现起来过于复杂,步骤繁琐(具体的步骤可以参考这篇文章:[Guide to JNI (Java Native Interface)](https://www.baeldung.com/jni)),不受 JVM 的语言安全机制控制,影响 Java 语言的跨平台特性。并且,JNI 的性能也不行,因为 JNI 方法调用不能从许多常见的 JIT 优化(如内联)中受益。虽然 [JNA](https://github.com/java-native-access/jna)、[JNR](https://github.com/jnr/jnr-ffi) 和 [JavaCPP](https://github.com/bytedeco/javacpp) 等框架对 JNI 进行了改进,但效果还是不太理想。
|
||||
|
||||
引入外部函数和内存 API 就是为了解决 Java 访问外部函数和外部内存存在的一些痛点。
|
||||
|
||||
Foreign Function & Memory API (FFM API) 定义了类和接口:
|
||||
|
||||
- 分配外部内存:`MemorySegment`、`MemoryAddress` 和 `SegmentAllocator`
|
||||
- 操作和访问结构化的外部内存:`MemoryLayout`、`VarHandle`
|
||||
- 控制外部内存的分配和释放:`MemorySession`
|
||||
- 调用外部函数:`Linker`、`FunctionDescriptor` 和 `SymbolLookup`
|
||||
|
||||
下面是 FFM API 使用示例,这段代码获取了 C 库函数的 `radixsort` 方法句柄,然后使用它对 Java 数组中的四个字符串进行排序。
|
||||
|
||||
```java
|
||||
// 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 = 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
|
||||
```
|
||||
|
||||
## JEP 425: 虚拟线程(预览)
|
||||
|
||||
虚拟线程(Virtual Thread)是 JDK 而不是 OS 实现的轻量级线程(Lightweight Process,LWP),许多虚拟线程共享同一个操作系统线程,虚拟线程的数量可以远大于操作系统线程的数量。
|
||||
|
||||
虚拟线程在其他多线程语言中已经被证实是十分有用的,比如 Go 中的 Goroutine、Erlang 中的进程。
|
||||
|
||||
虚拟线程避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂,可以有效减少编写、维护和观察高吞吐量并发应用程序的工作量。
|
||||
|
||||
知乎有一个关于 Java 19 虚拟线程的讨论,感兴趣的可以去看看:<https://www.zhihu.com/question/536743167>。
|
||||
|
||||
Java 虚拟线程的详细解读和原理可以看下面这两篇文章:
|
||||
|
||||
- [虚拟线程原理及性能分析|得物技术](https://mp.weixin.qq.com/s/vdLXhZdWyxc6K-D3Aj03LA)
|
||||
- [Java19 正式 GA!看虚拟线程如何大幅提高系统吞吐量](https://mp.weixin.qq.com/s/yyApBXxpXxVwttr01Hld6Q)
|
||||
- [虚拟线程 - VirtualThread 源码透视](https://www.cnblogs.com/throwable/p/16758997.html)
|
||||
|
||||
## JEP 426: 向量 API(第四次孵化)
|
||||
|
||||
向量(Vector) API 最初由 [JEP 338](https://openjdk.java.net/jeps/338) 提出,并作为[孵化 API](http://openjdk.java.net/jeps/11)集成到 Java 16 中。第二轮孵化由 [JEP 414](https://openjdk.java.net/jeps/414) 提出并集成到 Java 17 中,第三轮孵化由 [JEP 417](https://openjdk.java.net/jeps/417) 提出并集成到 Java 18 中,第四轮由 [JEP 426](https://openjdk.java.net/jeps/426) 提出并集成到了 Java 19 中。
|
||||
|
||||
在 [Java 18 新特性概览](./java18.md) 中,我有详细介绍到向量 API,这里就不再做额外的介绍了。
|
||||
|
||||
## JEP 428: 结构化并发(孵化)
|
||||
|
||||
JDK 19 引入了结构化并发,一种多线程编程方法,目的是为了通过结构化并发 API 来简化多线程编程,并不是为了取代 `java.util.concurrent`,目前处于孵化器阶段。
|
||||
|
||||
结构化并发将不同线程中运行的多个任务视为单个工作单元,从而简化错误处理、提高可靠性并增强可观察性。也就是说,结构化并发保留了单线程代码的可读性、可维护性和可观察性。
|
||||
|
||||
结构化并发的基本 API 是[`StructuredTaskScope`](https://download.java.net/java/early_access/loom/docs/api/jdk.incubator.concurrent/jdk/incubator/concurrent/StructuredTaskScope.html)。`StructuredTaskScope` 支持将任务拆分为多个并发子任务,在它们自己的线程中执行,并且子任务必须在主任务继续之前完成。
|
||||
|
||||
`StructuredTaskScope` 的基本用法如下:
|
||||
|
||||
```java
|
||||
try (var scope = new StructuredTaskScope<Object>()) {
|
||||
// 使用fork方法派生线程来执行子任务
|
||||
Future<Integer> future1 = scope.fork(task1);
|
||||
Future<String> future2 = scope.fork(task2);
|
||||
// 等待线程完成
|
||||
scope.join();
|
||||
// 结果的处理可能包括处理或重新抛出异常
|
||||
... process results/exceptions ...
|
||||
} // close
|
||||
```
|
||||
|
||||
结构化并发非常适合虚拟线程,虚拟线程是 JDK 实现的轻量级线程。许多虚拟线程共享同一个操作系统线程,从而允许非常多的虚拟线程。
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
@@ -0,0 +1,320 @@
|
||||
---
|
||||
title: Java 20 新特性概览
|
||||
description: 总结 JDK 20 的语言与并发改动,延续虚拟线程与模式匹配相关增强。
|
||||
category: Java
|
||||
tag:
|
||||
- Java新特性
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java 20,JDK20,记录模式预览,虚拟线程改进,语言增强,JEP
|
||||
---
|
||||
|
||||
JDK 20 于 2023 年 3 月 21 日发布,非长期支持版本。
|
||||
|
||||
根据开发计划,下一个 LTS 版本就是将于 2023 年 9 月发布的 JDK 21。
|
||||
|
||||
JDK 20 共有 7 个新特性,这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||||
|
||||
- [JEP 429: Scoped Values(作用域值)](https://openjdk.org/jeps/429)(第一次孵化)
|
||||
- [JEP 432: Record Patterns(记录模式)](https://openjdk.org/jeps/432)(第二次预览)
|
||||
- [JEP 433: Pattern Matching for switch(switch 模式匹配)](https://openjdk.org/jeps/433)(第四次预览)
|
||||
- [JEP 434: Foreign Function & Memory API(外部函数和内存 API)](https://openjdk.org/jeps/434)(第二次预览)
|
||||
- [JEP 436: Virtual Threads(虚拟线程)](https://openjdk.org/jeps/436)(第二次预览)
|
||||
- [JEP 437: Structured Concurrency(结构化并发)](https://openjdk.org/jeps/437)(第二次孵化)
|
||||
- [JEP 438: Vector API(向量 API)](https://openjdk.org/jeps/438)(第五次孵化)
|
||||
|
||||
下图是从 JDK 8 到 JDK 25 每个版本的更新带来的新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
## JEP 429: Scoped Values(作用域值,第一次孵化)
|
||||
|
||||
作用域值(Scoped Values)它可以在线程内和线程间共享不可变的数据,优于线程局部变量,尤其是在使用大量虚拟线程时。
|
||||
|
||||
```java
|
||||
final static ScopedValue<...> V = new ScopedValue<>();
|
||||
|
||||
// In some method
|
||||
ScopedValue.where(V, <value>)
|
||||
.run(() -> { ... V.get() ... call methods ... });
|
||||
|
||||
// In a method called directly or indirectly from the lambda expression
|
||||
... V.get() ...
|
||||
```
|
||||
|
||||
作用域值允许在大型程序中的组件之间安全有效地共享数据,而无需求助于方法参数。
|
||||
|
||||
关于作用域值的详细介绍,推荐阅读[作用域值常见问题解答](https://www.happycoders.eu/java/scoped-values/)这篇文章。
|
||||
|
||||
## JEP 432: Record Patterns(记录模式,第二次预览)
|
||||
|
||||
记录模式(Record Patterns) 可对 record 的值进行解构,也就是更方便地从记录类(Record Class)中提取数据。并且,还可以嵌套记录模式和类型模式结合使用,以实现强大的、声明性的和可组合的数据导航和处理形式。
|
||||
|
||||
记录模式不能单独使用,而是要与 instanceof 或 switch 模式匹配一同使用。
|
||||
|
||||
先以 instanceof 为例简单演示一下。
|
||||
|
||||
简单定义一个记录类:
|
||||
|
||||
```java
|
||||
record Shape(String type, long unit){}
|
||||
```
|
||||
|
||||
没有记录模式之前:
|
||||
|
||||
```java
|
||||
Shape circle = new Shape("Circle", 10);
|
||||
if (circle instanceof Shape shape) {
|
||||
|
||||
System.out.println("Area of " + shape.type() + " is : " + Math.PI * Math.pow(shape.unit(), 2));
|
||||
}
|
||||
```
|
||||
|
||||
有了记录模式之后:
|
||||
|
||||
```java
|
||||
Shape circle = new Shape("Circle", 10);
|
||||
if (circle instanceof Shape(String type, long unit)) {
|
||||
System.out.println("Area of " + type + " is : " + Math.PI * Math.pow(unit, 2));
|
||||
}
|
||||
```
|
||||
|
||||
再看看记录模式与 switch 的配合使用。
|
||||
|
||||
定义一些类:
|
||||
|
||||
```java
|
||||
interface Shape {}
|
||||
record Circle(double radius) implements Shape { }
|
||||
record Square(double side) implements Shape { }
|
||||
record Rectangle(double length, double width) implements Shape { }
|
||||
```
|
||||
|
||||
没有记录模式之前:
|
||||
|
||||
```java
|
||||
Shape shape = new Circle(10);
|
||||
switch (shape) {
|
||||
case Circle c:
|
||||
System.out.println("The shape is Circle with area: " + Math.PI * c.radius() * c.radius());
|
||||
break;
|
||||
|
||||
case Square s:
|
||||
System.out.println("The shape is Square with area: " + s.side() * s.side());
|
||||
break;
|
||||
|
||||
case Rectangle r:
|
||||
System.out.println("The shape is Rectangle with area: " + r.length() * r.width());
|
||||
break;
|
||||
|
||||
default:
|
||||
System.out.println("Unknown Shape");
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
有了记录模式之后:
|
||||
|
||||
```java
|
||||
Shape shape = new Circle(10);
|
||||
switch(shape) {
|
||||
|
||||
case Circle(double radius):
|
||||
System.out.println("The shape is Circle with area: " + Math.PI * radius * radius);
|
||||
break;
|
||||
|
||||
case Square(double side):
|
||||
System.out.println("The shape is Square with area: " + side * side);
|
||||
break;
|
||||
|
||||
case Rectangle(double length, double width):
|
||||
System.out.println("The shape is Rectangle with area: " + length * width);
|
||||
break;
|
||||
|
||||
default:
|
||||
System.out.println("Unknown Shape");
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
记录模式可以避免不必要的转换,使得代码更简洁易读。而且,用了记录模式后不必再担心 `null` 或者 `NullPointerException`,代码更安全可靠。
|
||||
|
||||
记录模式在 Java 19 进行了第一次预览, 由 [JEP 405](https://openjdk.org/jeps/405) 提出。JDK 20 中是第二次预览,由 [JEP 432](https://openjdk.org/jeps/432) 提出。这次的改进包括:
|
||||
|
||||
- 添加对通用记录模式类型参数推断的支持,
|
||||
- 添加对记录模式的支持以出现在增强语句的标题中 `for`
|
||||
- 删除对命名记录模式的支持。
|
||||
|
||||
**注意**:不要把记录模式和 [JDK16](./java16.md) 正式引入的记录类搞混了。
|
||||
|
||||
## JEP 433: Pattern Matching for switch(switch 模式匹配,第四次预览)
|
||||
|
||||
正如 `instanceof` 一样, `switch` 也紧跟着增加了类型匹配自动转换功能。
|
||||
|
||||
`instanceof` 代码示例:
|
||||
|
||||
```java
|
||||
// Old code
|
||||
if (o instanceof String) {
|
||||
String s = (String)o;
|
||||
... use s ...
|
||||
}
|
||||
|
||||
// New code
|
||||
if (o instanceof String s) {
|
||||
... use s ...
|
||||
}
|
||||
```
|
||||
|
||||
`switch` 代码示例:
|
||||
|
||||
```java
|
||||
// Old code
|
||||
static String formatter(Object o) {
|
||||
String formatted = "unknown";
|
||||
if (o instanceof Integer i) {
|
||||
formatted = String.format("int %d", i);
|
||||
} else if (o instanceof Long l) {
|
||||
formatted = String.format("long %d", l);
|
||||
} else if (o instanceof Double d) {
|
||||
formatted = String.format("double %f", d);
|
||||
} else if (o instanceof String s) {
|
||||
formatted = String.format("String %s", s);
|
||||
}
|
||||
return formatted;
|
||||
}
|
||||
|
||||
// New code
|
||||
static String formatterPatternSwitch(Object o) {
|
||||
return switch (o) {
|
||||
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 -> o.toString();
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
`switch` 模式匹配分别在 Java17、Java18、Java19 中进行了预览,Java20 是第四次预览了。每一次的预览基本都会有一些小改进,这里就不细提了。
|
||||
|
||||
## JEP 434: Foreign Function & Memory API(外部函数和内存 API,第二次预览)
|
||||
|
||||
Java 程序可以通过该 API 与 Java 运行时之外的代码和数据进行互操作。通过高效地调用外部函数(即 JVM 之外的代码)和安全地访问外部内存(即不受 JVM 管理的内存),该 API 使 Java 程序能够调用本机库并处理本机数据,而不会像 JNI 那样危险和脆弱。
|
||||
|
||||
外部函数和内存 API 在 Java 17 中进行了第一轮孵化,由 [JEP 412](https://openjdk.java.net/jeps/412) 提出。Java 18 中进行了第二次孵化,由[JEP 419](https://openjdk.org/jeps/419) 提出。Java 19 中是第一次预览,由 [JEP 424](https://openjdk.org/jeps/424) 提出。
|
||||
|
||||
JDK 20 中是第二次预览,由 [JEP 434](https://openjdk.org/jeps/434) 提出,这次的改进包括:
|
||||
|
||||
- `MemorySegment` 和 `MemoryAddress` 抽象的统一
|
||||
- 增强的 `MemoryLayout` 层次结构
|
||||
- `MemorySession` 拆分为 `Arena` 和 `SegmentScope`,以促进跨维护边界的段共享。
|
||||
|
||||
在 [Java 19 新特性概览](./java19.md) 中,我有详细介绍到外部函数和内存 API,这里就不再做额外的介绍了。
|
||||
|
||||
## JEP 436: Virtual Threads(虚拟线程,第二次预览)
|
||||
|
||||
虚拟线程(Virtual Thread)是 JDK 而不是 OS 实现的轻量级线程(Lightweight Process,LWP),由 JVM 调度。许多虚拟线程共享同一个操作系统线程,虚拟线程的数量可以远大于操作系统线程的数量。
|
||||
|
||||
在引入虚拟线程之前,`java.lang.Thread` 包已经支持所谓的平台线程,也就是没有虚拟线程之前,我们一直使用的线程。JVM 调度程序通过平台线程(载体线程)来管理虚拟线程,一个平台线程可以在不同的时间执行不同的虚拟线程(多个虚拟线程挂载在一个平台线程上),当虚拟线程被阻塞或等待时,平台线程可以切换到执行另一个虚拟线程。
|
||||
|
||||
虚拟线程、平台线程和系统内核线程的关系图如下所示(图源:[How to Use Java 19 Virtual Threads](https://medium.com/javarevisited/how-to-use-java-19-virtual-threads-c16a32bad5f7)):
|
||||
|
||||

|
||||
|
||||
关于平台线程和系统内核线程的对应关系多提一点:在 Windows 和 Linux 等主流操作系统中,Java 线程采用的是一对一的线程模型,也就是一个平台线程对应一个系统内核线程。Solaris 系统是一个特例,HotSpot VM 在 Solaris 上支持多对多和一对一。具体可以参考 R 大的回答: [JVM 中的线程模型是用户级的么?](https://www.zhihu.com/question/23096638/answer/29617153)。
|
||||
|
||||
相比较于平台线程来说,虚拟线程是廉价且轻量级的,使用完后立即被销毁,因此它们不需要被重用或池化,每个任务可以有自己专属的虚拟线程来运行。虚拟线程暂停和恢复来实现线程之间的切换,避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂,可以有效减少编写、维护和观察高吞吐量并发应用程序的工作量。
|
||||
|
||||
虚拟线程在其他多线程语言中已经被证实是十分有用的,比如 Go 中的 Goroutine、Erlang 中的进程。
|
||||
|
||||
知乎有一个关于 Java 19 虚拟线程的讨论,感兴趣的可以去看看:<https://www.zhihu.com/question/536743167>。
|
||||
|
||||
Java 虚拟线程的详细解读和原理可以看下面这几篇文章:
|
||||
|
||||
- [虚拟线程极简入门](https://javaguide.cn/java/concurrent/virtual-thread.html)
|
||||
- [Java19 正式 GA!看虚拟线程如何大幅提高系统吞吐量](https://mp.weixin.qq.com/s/yyApBXxpXxVwttr01Hld6Q)
|
||||
- [虚拟线程 - VirtualThread 源码透视](https://www.cnblogs.com/throwable/p/16758997.html)
|
||||
|
||||
虚拟线程在 Java 19 中进行了第一次预览,由[JEP 425](https://openjdk.org/jeps/425)提出。JDK 20 中是第二次预览,做了一些细微变化,这里就不细提了。
|
||||
|
||||
最后,我们来看一下四种创建虚拟线程的方法:
|
||||
|
||||
```java
|
||||
// 1、通过 Thread.ofVirtual() 创建
|
||||
Runnable fn = () -> {
|
||||
// your code here
|
||||
};
|
||||
|
||||
Thread thread = Thread.ofVirtual(fn)
|
||||
.start();
|
||||
|
||||
// 2、通过 Thread.startVirtualThread() 创建
|
||||
Thread thread = Thread.startVirtualThread(() -> {
|
||||
// your code here
|
||||
});
|
||||
|
||||
// 3、通过 Executors.newVirtualThreadPerTaskExecutor() 创建
|
||||
var executorService = Executors.newVirtualThreadPerTaskExecutor();
|
||||
|
||||
executorService.submit(() -> {
|
||||
// your code here
|
||||
});
|
||||
|
||||
class CustomThread implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("CustomThread run");
|
||||
}
|
||||
}
|
||||
|
||||
//4、通过 ThreadFactory 创建
|
||||
CustomThread customThread = new CustomThread();
|
||||
// 获取线程工厂类
|
||||
ThreadFactory factory = Thread.ofVirtual().factory();
|
||||
// 创建虚拟线程
|
||||
Thread thread = factory.newThread(customThread);
|
||||
// 启动线程
|
||||
thread.start();
|
||||
```
|
||||
|
||||
通过上述列举的 4 种创建虚拟线程的方式可以看出,官方为了降低虚拟线程的门槛,尽力复用原有的 `Thread` 线程类,这样可以平滑的过渡到虚拟线程的使用。
|
||||
|
||||
## JEP 437: Structured Concurrency(结构化并发,第二次孵化)
|
||||
|
||||
Java 19 引入了结构化并发,一种多线程编程方法,目的是为了通过结构化并发 API 来简化多线程编程,并不是为了取代 `java.util.concurrent`,目前处于孵化器阶段。
|
||||
|
||||
结构化并发将不同线程中运行的多个任务视为单个工作单元,从而简化错误处理、提高可靠性并增强可观察性。也就是说,结构化并发保留了单线程代码的可读性、可维护性和可观察性。
|
||||
|
||||
结构化并发的基本 API 是[`StructuredTaskScope`](https://download.java.net/java/early_access/loom/docs/api/jdk.incubator.concurrent/jdk/incubator/concurrent/StructuredTaskScope.html)。`StructuredTaskScope` 支持将任务拆分为多个并发子任务,在它们自己的线程中执行,并且子任务必须在主任务继续之前完成。
|
||||
|
||||
`StructuredTaskScope` 的基本用法如下:
|
||||
|
||||
```java
|
||||
try (var scope = new StructuredTaskScope<Object>()) {
|
||||
// 使用fork方法派生线程来执行子任务
|
||||
Future<Integer> future1 = scope.fork(task1);
|
||||
Future<String> future2 = scope.fork(task2);
|
||||
// 等待线程完成
|
||||
scope.join();
|
||||
// 结果的处理可能包括处理或重新抛出异常
|
||||
... process results/exceptions ...
|
||||
} // close
|
||||
```
|
||||
|
||||
结构化并发非常适合虚拟线程,虚拟线程是 JDK 实现的轻量级线程。许多虚拟线程共享同一个操作系统线程,从而允许非常多的虚拟线程。
|
||||
|
||||
JDK 20 中对结构化并发唯一变化是更新为支持在任务范围内创建的线程 `StructuredTaskScope` 继承范围值 这简化了跨线程共享不可变数据,详见[JEP 429](https://openjdk.org/jeps/429)。
|
||||
|
||||
## JEP 438: Vector API(向量 API,第五次孵化)
|
||||
|
||||
向量计算由对向量的一系列操作组成。向量 API 用来表达向量计算,该计算可以在运行时可靠地编译为支持的 CPU 架构上的最佳向量指令,从而实现优于等效标量计算的性能。
|
||||
|
||||
向量 API 的目标是为用户提供简洁易用且与平台无关的表达范围广泛的向量计算。
|
||||
|
||||
向量(Vector) API 最初由 [JEP 338](https://openjdk.java.net/jeps/338) 提出,并作为[孵化 API](http://openjdk.java.net/jeps/11)集成到 Java 16 中。第二轮孵化由 [JEP 414](https://openjdk.java.net/jeps/414) 提出并集成到 Java 17 中,第三轮孵化由 [JEP 417](https://openjdk.java.net/jeps/417) 提出并集成到 Java 18 中,第四轮由 [JEP 426](https://openjdk.java.net/jeps/426) 提出并集成到了 Java 19 中。
|
||||
|
||||
Java20 的这次孵化基本没有改变向量 API,只是进行了一些错误修复和性能增强,详见 [JEP 438](https://openjdk.org/jeps/438)。
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
@@ -0,0 +1,384 @@
|
||||
---
|
||||
title: Java 21 新特性概览(重要)
|
||||
description: 概览 JDK 21 的关键新特性与实践影响,重点介绍字符串模板、Sequenced Collections、分代 ZGC、虚拟线程等。
|
||||
category: Java
|
||||
tag:
|
||||
- Java新特性
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java 21,JDK21,LTS,字符串模板,Sequenced Collections,分代 ZGC,记录模式,switch 模式匹配,虚拟线程,外部函数与内存 API
|
||||
---
|
||||
|
||||
JDK 21 于 2023 年 9 月 19 日 发布,这是一个非常重要的版本,里程碑式。
|
||||
|
||||
JDK 21 是 LTS(长期支持版),至此为止,目前有 JDK8、JDK11、JDK17 和 JDK21 这四个长期支持版了。
|
||||
|
||||
JDK 21 共有 15 个新特性,这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||||
|
||||
- [JEP 430: String Templates(字符串模板)](https://openjdk.org/jeps/430)(预览)
|
||||
- [JEP 431: Sequenced Collections(序列化集合)](https://openjdk.org/jeps/431)
|
||||
- [JEP 439: Generational ZGC(分代 ZGC)](https://openjdk.org/jeps/439)
|
||||
- [JEP 440: Record Patterns(记录模式)](https://openjdk.org/jeps/440)
|
||||
- [JEP 441: Pattern Matching for switch(switch 的模式匹配)](https://openjdk.org/jeps/441)
|
||||
- [JEP 442: Foreign Function & Memory API(外部函数和内存 API)](https://openjdk.org/jeps/442)(第三次预览)
|
||||
- [JEP 443: Unnamed Patterns and Variables(未命名模式和变量)](https://openjdk.org/jeps/443)(预览)
|
||||
- [JEP 444: Virtual Threads(虚拟线程)](https://openjdk.org/jeps/444)
|
||||
- [JEP 445: Unnamed Classes and Instance Main Methods(未命名类和实例 main 方法)](https://openjdk.org/jeps/445)(预览)
|
||||
|
||||
下图是从 JDK 8 到 JDK 24 每个版本的更新带来的新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
## JEP 430: String Templates(字符串模板,预览)
|
||||
|
||||
String Templates(字符串模板) 目前仍然是 JDK 21 中的一个预览功能。
|
||||
|
||||
String Templates 提供了一种更简洁、更直观的方式来动态构建字符串。通过使用占位符 `${}`,我们可以将变量的值直接嵌入到字符串中,而不需要手动处理。在运行时,Java 编译器会将这些占位符替换为实际的变量值。并且,表达式支持局部变量、静态/非静态字段甚至方法、计算结果等特性。
|
||||
|
||||
实际上,String Templates(字符串模板)在大多数编程语言中都存在:
|
||||
|
||||
```typescript
|
||||
"Greetings {{ name }}!"; //Angular
|
||||
`Greetings ${ name }!`; //Typescript
|
||||
$"Greetings { name }!" //Visual basic
|
||||
f"Greetings { name }!" //Python
|
||||
```
|
||||
|
||||
Java 在没有 String Templates 之前,我们通常使用字符串拼接或格式化方法来构建字符串:
|
||||
|
||||
```java
|
||||
//concatenation
|
||||
message = "Greetings " + name + "!";
|
||||
|
||||
//String.format()
|
||||
message = String.format("Greetings %s!", name); //concatenation
|
||||
|
||||
//MessageFormat
|
||||
message = new MessageFormat("Greetings {0}!").format(name);
|
||||
|
||||
//StringBuilder
|
||||
message = new StringBuilder().append("Greetings ").append(name).append("!").toString();
|
||||
```
|
||||
|
||||
这些方法或多或少都存在一些缺点,比如难以阅读、冗长、复杂。
|
||||
|
||||
Java 使用 String Templates 进行字符串拼接,可以直接在字符串中嵌入表达式,而无需进行额外的处理:
|
||||
|
||||
```java
|
||||
String message = STR."Greetings \{name}!";
|
||||
```
|
||||
|
||||
在上面的模板表达式中:
|
||||
|
||||
- STR 是模板处理器。
|
||||
- `\{name}` 为表达式,运行时,这些表达式将被相应的变量值替换。
|
||||
|
||||
Java 目前支持三种模板处理器:
|
||||
|
||||
- STR:自动执行字符串插值,即将模板中的每个嵌入式表达式替换为其值(转换为字符串)。
|
||||
- FMT:和 STR 类似,但是它还可以接受格式说明符,这些格式说明符出现在嵌入式表达式的左边,用来控制输出的样式。
|
||||
- RAW:不会像 STR 和 FMT 模板处理器那样自动处理字符串模板,而是返回一个 `StringTemplate` 对象,这个对象包含了模板中的文本和表达式的信息。
|
||||
|
||||
```java
|
||||
String name = "Lokesh";
|
||||
|
||||
//STR
|
||||
String message = STR."Greetings \{name}.";
|
||||
|
||||
//FMT
|
||||
String message = FMT."Greetings %-12s\{name}.";
|
||||
|
||||
//RAW
|
||||
StringTemplate st = RAW."Greetings \{name}.";
|
||||
String message = STR.process(st);
|
||||
```
|
||||
|
||||
除了 JDK 自带的三种模板处理器外,你还可以实现 `StringTemplate.Processor` 接口来创建自己的模板处理器,只需要继承 `StringTemplate.Processor` 接口,然后实现 `process` 方法即可。
|
||||
|
||||
我们可以使用局部变量、静态/非静态字段甚至方法作为嵌入表达式:
|
||||
|
||||
```java
|
||||
//variable
|
||||
message = STR."Greetings \{name}!";
|
||||
|
||||
//method
|
||||
message = STR."Greetings \{getName()}!";
|
||||
|
||||
//field
|
||||
message = STR."Greetings \{this.name}!";
|
||||
```
|
||||
|
||||
还可以在表达式中执行计算并打印结果:
|
||||
|
||||
```java
|
||||
int x = 10, y = 20;
|
||||
String s = STR."\{x} + \{y} = \{x + y}"; //"10 + 20 = 30"
|
||||
```
|
||||
|
||||
为了提高可读性,我们可以将嵌入的表达式分成多行:
|
||||
|
||||
```java
|
||||
String time = STR."The current time is \{
|
||||
//sample comment - current time in HH:mm:ss
|
||||
DateTimeFormatter
|
||||
.ofPattern("HH:mm:ss")
|
||||
.format(LocalTime.now())
|
||||
}.";
|
||||
```
|
||||
|
||||
## JEP 431: Sequenced Collections(序列化集合)
|
||||
|
||||
JDK 21 引入了一种新的集合类型:**Sequenced Collections(序列化集合,也叫有序集合)**,这是一种具有确定出现顺序(encounter order)的集合(无论我们遍历这样的集合多少次,元素的出现顺序始终是固定的)。序列化集合提供了处理集合的第一个和最后一个元素以及反向视图(与原始集合相反的顺序)的简单方法。
|
||||
|
||||
Sequenced Collections 包括以下三个接口:
|
||||
|
||||
- [`SequencedCollection`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/SequencedCollection.html)
|
||||
- [`SequencedSet`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/SequencedSet.html)
|
||||
- [`SequencedMap`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/SequencedMap.html)
|
||||
|
||||
`SequencedCollection` 接口继承了 `Collection` 接口, 提供了在集合两端访问、添加或删除元素以及获取集合的反向视图的方法。
|
||||
|
||||
```java
|
||||
interface SequencedCollection<E> extends Collection<E> {
|
||||
|
||||
// New Method
|
||||
|
||||
SequencedCollection<E> reversed();
|
||||
|
||||
// Promoted methods from Deque<E>
|
||||
|
||||
void addFirst(E);
|
||||
void addLast(E);
|
||||
|
||||
E getFirst();
|
||||
E getLast();
|
||||
|
||||
E removeFirst();
|
||||
E removeLast();
|
||||
}
|
||||
```
|
||||
|
||||
`List` 和 `Deque` 接口实现了 `SequencedCollection` 接口。
|
||||
|
||||
这里以 `ArrayList` 为例,演示一下实际使用效果:
|
||||
|
||||
```java
|
||||
ArrayList<Integer> arrayList = new ArrayList<>();
|
||||
|
||||
arrayList.add(1); // List contains: [1]
|
||||
|
||||
arrayList.addFirst(0); // List contains: [0, 1]
|
||||
arrayList.addLast(2); // List contains: [0, 1, 2]
|
||||
|
||||
Integer firstElement = arrayList.getFirst(); // 0
|
||||
Integer lastElement = arrayList.getLast(); // 2
|
||||
|
||||
List<Integer> reversed = arrayList.reversed();
|
||||
System.out.println(reversed); // Prints [2, 1, 0]
|
||||
```
|
||||
|
||||
`SequencedSet` 接口直接继承了 `SequencedCollection` 接口并重写了 `reversed()` 方法。
|
||||
|
||||
```java
|
||||
interface SequencedSet<E> extends SequencedCollection<E>, Set<E> {
|
||||
|
||||
SequencedSet<E> reversed();
|
||||
}
|
||||
```
|
||||
|
||||
`SortedSet` 和 `LinkedHashSet` 实现了 `SequencedSet` 接口。
|
||||
|
||||
这里以 `LinkedHashSet` 为例,演示一下实际使用效果:
|
||||
|
||||
```java
|
||||
LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>(List.of(1, 2, 3));
|
||||
|
||||
Integer firstElement = linkedHashSet.getFirst(); // 1
|
||||
Integer lastElement = linkedHashSet.getLast(); // 3
|
||||
|
||||
linkedHashSet.addFirst(0); //List contains: [0, 1, 2, 3]
|
||||
linkedHashSet.addLast(4); //List contains: [0, 1, 2, 3, 4]
|
||||
|
||||
System.out.println(linkedHashSet.reversed()); //Prints [4, 3, 2, 1, 0]
|
||||
```
|
||||
|
||||
`SequencedMap` 接口继承了 `Map` 接口, 提供了在集合两端访问、添加或删除键值对、获取包含 key 的 `SequencedSet`、包含 value 的 `SequencedCollection`、包含 entry(键值对) 的 `SequencedSet` 以及获取集合的反向视图的方法。
|
||||
|
||||
```java
|
||||
interface SequencedMap<K,V> extends Map<K,V> {
|
||||
|
||||
// New Methods
|
||||
|
||||
SequencedMap<K,V> reversed();
|
||||
|
||||
SequencedSet<K> sequencedKeySet();
|
||||
SequencedCollection<V> sequencedValues();
|
||||
SequencedSet<Entry<K,V>> sequencedEntrySet();
|
||||
|
||||
V putFirst(K, V);
|
||||
V putLast(K, V);
|
||||
|
||||
|
||||
// Promoted Methods from NavigableMap<K, V>
|
||||
|
||||
Entry<K, V> firstEntry();
|
||||
Entry<K, V> lastEntry();
|
||||
|
||||
Entry<K, V> pollFirstEntry();
|
||||
Entry<K, V> pollLastEntry();
|
||||
}
|
||||
```
|
||||
|
||||
`SortedMap` 和 `LinkedHashMap` 实现了 `SequencedMap` 接口。
|
||||
|
||||
这里以 `LinkedHashMap` 为例,演示一下实际使用效果:
|
||||
|
||||
```java
|
||||
LinkedHashMap<Integer, String> map = new LinkedHashMap<>();
|
||||
|
||||
map.put(1, "One");
|
||||
map.put(2, "Two");
|
||||
map.put(3, "Three");
|
||||
|
||||
map.firstEntry(); //1=One
|
||||
map.lastEntry(); //3=Three
|
||||
|
||||
System.out.println(map); //{1=One, 2=Two, 3=Three}
|
||||
|
||||
Map.Entry<Integer, String> first = map.pollFirstEntry(); //1=One
|
||||
Map.Entry<Integer, String> last = map.pollLastEntry(); //3=Three
|
||||
|
||||
System.out.println(map); //{2=Two}
|
||||
|
||||
map.putFirst(1, "One"); //{1=One, 2=Two}
|
||||
map.putLast(3, "Three"); //{1=One, 2=Two, 3=Three}
|
||||
|
||||
System.out.println(map); //{1=One, 2=Two, 3=Three}
|
||||
System.out.println(map.reversed()); //{3=Three, 2=Two, 1=One}
|
||||
```
|
||||
|
||||
## JEP 439: Generational ZGC(分代 ZGC)
|
||||
|
||||
JDK21 中对 ZGC 进行了功能扩展,增加了分代 GC 功能。不过,默认是关闭的,需要通过配置打开:
|
||||
|
||||
```bash
|
||||
// 启用分代ZGC
|
||||
java -XX:+UseZGC -XX:+ZGenerational ...
|
||||
```
|
||||
|
||||
在未来的版本中,官方会把 ZGenerational 设为默认值,即默认打开 ZGC 的分代 GC。在更晚的版本中,非分代 ZGC 就被移除。
|
||||
|
||||
> In a future release we intend to make Generational ZGC the default, at which point -XX:-ZGenerational will select non-generational ZGC. In an even later release we intend to remove non-generational ZGC, at which point the ZGenerational option will become obsolete.
|
||||
>
|
||||
> 在将来的版本中,我们打算将 Generational ZGC 作为默认选项,此时-XX:-ZGenerational 将选择非分代 ZGC。在更晚的版本中,我们打算移除非分代 ZGC,此时 ZGenerational 选项将变得过时。
|
||||
|
||||
分代 ZGC 可以显著减少垃圾回收过程中的停顿时间,并提高应用程序的响应性能。这对于大型 Java 应用程序和高并发场景下的性能优化非常有价值。
|
||||
|
||||
## JEP 440: Record Patterns(记录模式)
|
||||
|
||||
记录模式在 Java 19 进行了第一次预览, 由 [JEP 405](https://openjdk.org/jeps/405) 提出。JDK 20 中是第二次预览,由 [JEP 432](https://openjdk.org/jeps/432) 提出。最终,记录模式在 JDK21 顺利转正。
|
||||
|
||||
[Java 20 新特性概览](./java20.md)已经详细介绍过记录模式,这里就不重复了。
|
||||
|
||||
## JEP 441: Pattern Matching for switch(switch 的模式匹配)
|
||||
|
||||
增强 Java 中的 switch 表达式和语句,允许在 case 标签中使用模式。当模式匹配时,执行 case 标签对应的代码。
|
||||
|
||||
在下面的代码中,switch 表达式使用了类型模式来进行匹配。
|
||||
|
||||
```java
|
||||
static String formatterPatternSwitch(Object obj) {
|
||||
return switch (obj) {
|
||||
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 -> obj.toString();
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## JEP 442: Foreign Function & Memory API(外部函数和内存 API,第三次预览)
|
||||
|
||||
Java 程序可以通过该 API 与 Java 运行时之外的代码和数据进行互操作。通过高效地调用外部函数(即 JVM 之外的代码)和安全地访问外部内存(即不受 JVM 管理的内存),该 API 使 Java 程序能够调用本机库并处理本机数据,而不会像 JNI 那样危险和脆弱。
|
||||
|
||||
外部函数和内存 API 在 Java 17 中进行了第一轮孵化,由 [JEP 412](https://openjdk.java.net/jeps/412) 提出。Java 18 中进行了第二次孵化,由[JEP 419](https://openjdk.org/jeps/419) 提出。Java 19 中是第一次预览,由 [JEP 424](https://openjdk.org/jeps/424) 提出。JDK 20 中是第二次预览,由 [JEP 434](https://openjdk.org/jeps/434) 提出。JDK 21 中是第三次预览,由 [JEP 442](https://openjdk.org/jeps/442) 提出。
|
||||
|
||||
在 [Java 19 新特性概览](./java19.md) 中,我有详细介绍到外部函数和内存 API,这里就不再做额外的介绍了。
|
||||
|
||||
## JEP 443: Unnamed Patterns and Variables(未命名模式和变量,预览)
|
||||
|
||||
未命名模式和变量使得我们可以使用下划线 `_` 表示未命名的变量以及模式匹配时不使用的组件,旨在提高代码的可读性和可维护性。
|
||||
|
||||
未命名变量的典型场景是 `try-with-resources` 语句、 `catch` 子句中的异常变量和 `for` 循环。当变量不需要使用的时候就可以使用下划线 `_` 代替,这样清晰标识未被使用的变量。
|
||||
|
||||
```java
|
||||
try (var _ = ScopedContext.acquire()) {
|
||||
// No use of acquired resource
|
||||
}
|
||||
try { ... }
|
||||
catch (Exception _) { ... }
|
||||
catch (Throwable _) { ... }
|
||||
|
||||
for (int i = 0, _ = runOnce(); i < arr.length; i++) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
未命名模式是一个无条件的模式,并不绑定任何值。未命名模式变量出现在类型模式中。
|
||||
|
||||
```java
|
||||
if (r instanceof ColoredPoint(_, Color c)) { ... c ... }
|
||||
|
||||
switch (b) {
|
||||
case Box(RedBall _), Box(BlueBall _) -> processBox(b);
|
||||
case Box(GreenBall _) -> stopProcessing();
|
||||
case Box(_) -> pickAnotherBox();
|
||||
}
|
||||
```
|
||||
|
||||
## JEP 444: Virtual Threads(虚拟线程)
|
||||
|
||||
虚拟线程是一项重量级的更新,一定一定要重视!
|
||||
|
||||
虚拟线程在 Java 19 中进行了第一次预览,由[JEP 425](https://openjdk.org/jeps/425)提出。JDK 20 中是第二次预览。最终,虚拟线程在 JDK21 顺利转正。
|
||||
|
||||
[Java 20 新特性概览](./java20.md)已经详细介绍过虚拟线程,这里就不重复了。
|
||||
|
||||
## JEP 445: Unnamed Classes and Instance Main Methods(未命名类和实例 main 方法,预览)
|
||||
|
||||
这个特性主要简化了 `main` 方法的声明。对于 Java 初学者来说,这个 `main` 方法的声明引入了太多的 Java 语法概念,不利于初学者快速上手。
|
||||
|
||||
没有使用该特性之前定义一个 `main` 方法:
|
||||
|
||||
```java
|
||||
public class HelloWorld {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, World!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
使用该新特性之后定义一个 `main` 方法:
|
||||
|
||||
```java
|
||||
class HelloWorld {
|
||||
void main() {
|
||||
System.out.println("Hello, World!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
进一步精简(未命名的类允许我们不定义类名):
|
||||
|
||||
```java
|
||||
void main() {
|
||||
System.out.println("Hello, World!");
|
||||
}
|
||||
```
|
||||
|
||||
## 参考
|
||||
|
||||
- Java 21 String Templates:<https://howtodoinjava.com/java/java-string-templates/>
|
||||
- Java 21 Sequenced Collections:<https://howtodoinjava.com/java/sequenced-collections/>
|
||||
@@ -0,0 +1,434 @@
|
||||
---
|
||||
title: Java 22 & 23 新特性概览
|
||||
description: 概览 JDK 22/23 的关键 JEP 与语言/平台增强,持续追踪性能与并发相关改动。
|
||||
category: Java
|
||||
tag:
|
||||
- Java新特性
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java 22,Java 23,JEP,Markdown 文档注释,类文件 API,向量 API,结构化并发,作用域值
|
||||
---
|
||||
|
||||
JDK 23 和 JDK 22 一样,这也是一个非 LTS(长期支持)版本,Oracle 仅提供六个月的支持。下一个长期支持版是 JDK 25,预计于 2025 年 9 月发布。
|
||||
|
||||
下图是从 JDK8 到 JDK 24 每个版本的更新带来的新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
由于 JDK 22 和 JDK 23 重合的新特性较多,这里主要以 JDK 23 为主介绍,会补充 JDK 22 独有的一些特性。
|
||||
|
||||
JDK 23 一共有 12 个新特性:
|
||||
|
||||
- [JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview)(模式中的原始类型、instanceof 和 switch,预览)](https://openjdk.org/jeps/455)
|
||||
- [JEP 466: Class File API (Second Preview)(类文件 API,第二次预览)](https://openjdk.org/jeps/466)
|
||||
- [JEP 467: Markdown Documentation Comments(Markdown 文档注释)](https://openjdk.org/jeps/467)
|
||||
- [JEP 469: Vector API (Eighth Incubator)(向量 API,第八次孵化)](https://openjdk.org/jeps/469)
|
||||
- [JEP 473: Stream Gatherers (Second Preview)(流收集器,第二次预览)](https://openjdk.org/jeps/473)
|
||||
- [JEP 471: Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal(弃用 sun.misc.Unsafe 中的内存访问方法以便移除)](https://openjdk.org/jeps/471)
|
||||
- [JEP 474: ZGC: Generational Mode by Default(ZGC:默认的分代模式)](https://openjdk.org/jeps/474)
|
||||
- [JEP 476: Module Import Declarations (Preview)(模块导入声明,预览)](https://openjdk.org/jeps/476)
|
||||
- [JEP 477: Unnamed Classes and Instance Main Methods (Third Preview)(未命名类和实例 main 方法,第三次预览)](https://openjdk.org/jeps/477)
|
||||
- [JEP 480: Structured Concurrency (Third Preview)(结构化并发,第三次预览)](https://openjdk.org/jeps/480)
|
||||
- [JEP 481: Scoped Values (Third Preview)(作用域值,第三次预览)](https://openjdk.org/jeps/481)
|
||||
- [JEP 482: Flexible Constructor Bodies (Second Preview)(灵活的构造函数体,第二次预览)](https://openjdk.org/jeps/482)
|
||||
|
||||
JDK 22 共有 12 个新特性,如下所示:
|
||||
|
||||

|
||||
|
||||
其中,下面这 4 条新特性我会单独拎出来详细介绍一下:
|
||||
|
||||
- [JEP 423:G1 垃圾收集器区域固定](https://openjdk.org/jeps/423)
|
||||
- [JEP 454:外部函数与内存 API](https://openjdk.org/jeps/454)
|
||||
- [JEP 456:未命名模式和变量](https://openjdk.org/jeps/456)
|
||||
- [JEP 458:启动多文件源代码程序](https://openjdk.org/jeps/458)
|
||||
|
||||
## JDK 23
|
||||
|
||||
### JEP 455: 模式中的原始类型、instanceof 和 switch(预览)
|
||||
|
||||
在 JEP 455 之前, `instanceof` 只支持引用类型,`switch` 表达式和语句的 `case` 标签只能使用整数字面量、枚举常量和字符串字面量。
|
||||
|
||||
JEP 455 的预览特性中,`instanceof` 和 `switch` 全面支持所有原始类型,包括 `byte`, `short`, `char`, `int`, `long`, `float`, `double`, `boolean`。
|
||||
|
||||
```java
|
||||
// 传统写法
|
||||
if (i >= -128 && i <= 127) {
|
||||
byte b = (byte)i;
|
||||
... b ...
|
||||
}
|
||||
|
||||
// 使用 instanceof 改进
|
||||
if (i instanceof byte b) {
|
||||
... b ...
|
||||
}
|
||||
|
||||
long v = ...;
|
||||
// 传统写法
|
||||
if (v == 1L) {
|
||||
// ...
|
||||
} else if (v == 2L) {
|
||||
// ...
|
||||
} else if (v == 10_000_000_000L) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// 使用 long 类型的 case 标签
|
||||
switch (v) {
|
||||
case 1L:
|
||||
// ...
|
||||
break;
|
||||
case 2L:
|
||||
// ...
|
||||
break;
|
||||
case 10_000_000_000L:
|
||||
// ...
|
||||
break;
|
||||
default:
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### JEP 466: Class File API(第二次预览)
|
||||
|
||||
类文件 API 在 JDK 22 进行了第一次预览,由 [JEP 457](https://openjdk.org/jeps/457) 提出。
|
||||
|
||||
类文件 API 的目标是提供一套标准化的 API,用于解析、生成和转换 Java 类文件,取代过去对第三方库(如 ASM)在类文件处理上的依赖。
|
||||
|
||||
```java
|
||||
// 创建一个 ClassFile 对象,这是操作类文件的入口。
|
||||
ClassFile cf = ClassFile.of();
|
||||
// 解析字节数组为 ClassModel
|
||||
ClassModel classModel = cf.parse(bytes);
|
||||
|
||||
// 构建新的类文件,移除以 "debug" 开头的所有方法
|
||||
byte[] newBytes = cf.build(classModel.thisClass().asSymbol(),
|
||||
classBuilder -> {
|
||||
// 遍历所有类元素
|
||||
for (ClassElement ce : classModel) {
|
||||
// 判断是否为方法 且 方法名以 "debug" 开头
|
||||
if (!(ce instanceof MethodModel mm
|
||||
&& mm.methodName().stringValue().startsWith("debug"))) {
|
||||
// 添加到新的类文件中
|
||||
classBuilder.with(ce);
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### JEP 467:Markdown 文档注释
|
||||
|
||||
在 JavaDoc 文档注释中可以使用 Markdown 语法,取代原本只能使用 HTML 和 JavaDoc 标签的方式。
|
||||
|
||||
Markdown 更简洁易读,减少了手动编写 HTML 的繁琐,同时保留了对 HTML 元素和 JavaDoc 标签的支持。这个增强旨在让 API 文档注释的编写和阅读变得更加轻松,同时不会影响现有注释的解释。Markdown 提供了对常见文档元素(如段落、列表、链接等)的简化表达方式,提升了文档注释的可维护性和开发者体验。
|
||||
|
||||

|
||||
|
||||
### JEP 469:向量 API(第八次孵化)
|
||||
|
||||
向量计算由对向量的一系列操作组成。向量 API 用来表达向量计算,该计算可以在运行时可靠地编译为支持的 CPU 架构上的最佳向量指令,从而实现优于等效标量计算的性能。
|
||||
|
||||
向量 API 的目标是为用户提供简洁易用且与平台无关的表达范围广泛的向量计算。
|
||||
|
||||
这是对数组元素的简单标量计算:
|
||||
|
||||
```java
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这是使用 Vector API 进行的等效向量计算:
|
||||
|
||||
```java
|
||||
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
|
||||
|
||||
void vectorComputation(float[] a, float[] b, float[] c) {
|
||||
int i = 0;
|
||||
int upperBound = SPECIES.loopBound(a.length);
|
||||
for (; i < upperBound; i += SPECIES.length()) {
|
||||
// FloatVector va, vb, vc;
|
||||
var va = FloatVector.fromArray(SPECIES, a, i);
|
||||
var vb = FloatVector.fromArray(SPECIES, b, i);
|
||||
var vc = va.mul(va)
|
||||
.add(vb.mul(vb))
|
||||
.neg();
|
||||
vc.intoArray(c, i);
|
||||
}
|
||||
for (; i < a.length; i++) {
|
||||
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### JEP 473:流收集器(第二次预览)
|
||||
|
||||
流收集器在 JDK 22 进行了第一次预览,由 [JEP 461](https://openjdk.org/jeps/461) 提出。
|
||||
|
||||
这个改进使得 Stream API 可以支持自定义中间操作。
|
||||
|
||||
```java
|
||||
source.gather(a).gather(b).gather(c).collect(...)
|
||||
```
|
||||
|
||||
### JEP 471:弃用 sun.misc.Unsafe 中的内存访问方法
|
||||
|
||||
JEP 471 提议弃用 `sun.misc.Unsafe` 中的内存访问方法,这些方法将来的版本中会被移除。
|
||||
|
||||
这些不安全的方法已有安全高效的替代方案:
|
||||
|
||||
- `java.lang.invoke.VarHandle`:JDK 9 (JEP 193) 中引入,提供了一种安全有效地操作堆内存的方法,包括对象的字段、类的静态字段以及数组元素。
|
||||
- `java.lang.foreign.MemorySegment`:JDK 22 (JEP 454) 中引入,提供了一种安全有效地访问堆外内存的方法,有时会与 `VarHandle` 协同工作。
|
||||
|
||||
这两个类是 Foreign Function & Memory API(外部函数和内存 API) 的核心组件,分别用于管理和操作堆外内存。Foreign Function & Memory API 在 JDK 22 中正式转正,成为标准特性。
|
||||
|
||||
```java
|
||||
import jdk.incubator.foreign.*;
|
||||
import java.lang.invoke.VarHandle;
|
||||
|
||||
// 管理堆外整数数组的类
|
||||
class OffHeapIntBuffer {
|
||||
|
||||
// 用于访问整数元素的VarHandle
|
||||
private static final VarHandle ELEM_VH = ValueLayout.JAVA_INT.arrayElementVarHandle();
|
||||
|
||||
// 内存管理器
|
||||
private final Arena arena;
|
||||
|
||||
// 堆外内存段
|
||||
private final MemorySegment buffer;
|
||||
|
||||
// 构造函数,分配指定数量的整数空间
|
||||
public OffHeapIntBuffer(long size) {
|
||||
this.arena = Arena.ofShared();
|
||||
this.buffer = arena.allocate(ValueLayout.JAVA_INT, size);
|
||||
}
|
||||
|
||||
// 释放内存
|
||||
public void deallocate() {
|
||||
arena.close();
|
||||
}
|
||||
|
||||
// 以volatile方式设置指定索引的值
|
||||
public void setVolatile(long index, int value) {
|
||||
ELEM_VH.setVolatile(buffer, 0L, index, value);
|
||||
}
|
||||
|
||||
// 初始化指定范围的元素为0
|
||||
public void initialize(long start, long n) {
|
||||
buffer.asSlice(ValueLayout.JAVA_INT.byteSize() * start,
|
||||
ValueLayout.JAVA_INT.byteSize() * n)
|
||||
.fill((byte) 0);
|
||||
}
|
||||
|
||||
// 将指定范围的元素复制到新数组
|
||||
public int[] copyToNewArray(long start, int n) {
|
||||
return buffer.asSlice(ValueLayout.JAVA_INT.byteSize() * start,
|
||||
ValueLayout.JAVA_INT.byteSize() * n)
|
||||
.toArray(ValueLayout.JAVA_INT);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### JEP 474:ZGC:默认的分代模式
|
||||
|
||||
Z 垃圾回收器 (ZGC) 的默认模式切换为分代模式,并弃用非分代模式,计划在未来版本中移除。这是因为分代 ZGC 是大多数场景下的更优选择。
|
||||
|
||||
### JEP 476:模块导入声明(预览)
|
||||
|
||||
模块导入声明允许在 Java 代码中简洁地导入整个模块的所有导出包,而无需逐个声明包的导入。这一特性简化了模块化库的重用,特别是在使用多个模块时,避免了大量的包导入声明,使得开发者可以更方便地访问第三方库和 Java 基本类。
|
||||
|
||||
此特性对初学者和原型开发尤为有用,因为它无需开发者将自己的代码模块化,同时保留了对传统导入方式的兼容性,提升了开发效率和代码可读性。
|
||||
|
||||
```java
|
||||
// 导入整个 java.base 模块,开发者可以直接访问 List、Map、Stream 等类,而无需每次手动导入相关包
|
||||
import module java.base;
|
||||
|
||||
public class Example {
|
||||
public static void main(String[] args) {
|
||||
String[] fruits = { "apple", "berry", "citrus" };
|
||||
Map<String, String> fruitMap = Stream.of(fruits)
|
||||
.collect(Collectors.toMap(
|
||||
s -> s.toUpperCase().substring(0, 1),
|
||||
Function.identity()));
|
||||
|
||||
System.out.println(fruitMap);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### JEP 477:未命名类和实例 main 方法(第三次预览)
|
||||
|
||||
这个特性主要简化了 `main` 方法的声明。对于 Java 初学者来说,这个 `main` 方法的声明引入了太多的 Java 语法概念,不利于初学者快速上手。
|
||||
|
||||
没有使用该特性之前定义一个 `main` 方法:
|
||||
|
||||
```java
|
||||
public class HelloWorld {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, World!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
使用该新特性之后定义一个 `main` 方法:
|
||||
|
||||
```java
|
||||
class HelloWorld {
|
||||
void main() {
|
||||
System.out.println("Hello, World!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
进一步简化(未命名的类允许我们省略类名)
|
||||
|
||||
```java
|
||||
void main() {
|
||||
System.out.println("Hello, World!");
|
||||
}
|
||||
```
|
||||
|
||||
### JEP 480:结构化并发(第三次预览)
|
||||
|
||||
Java 19 引入了结构化并发,一种多线程编程方法,目的是为了通过结构化并发 API 来简化多线程编程,并不是为了取代 `java.util.concurrent`,目前处于孵化器阶段。
|
||||
|
||||
结构化并发将不同线程中运行的多个任务视为单个工作单元,从而简化错误处理、提高可靠性并增强可观察性。也就是说,结构化并发保留了单线程代码的可读性、可维护性和可观察性。
|
||||
|
||||
结构化并发的基本 API 是[`StructuredTaskScope`](https://download.java.net/java/early_access/loom/docs/api/jdk.incubator.concurrent/jdk/incubator/concurrent/StructuredTaskScope.html)。`StructuredTaskScope` 支持将任务拆分为多个并发子任务,在它们自己的线程中执行,并且子任务必须在主任务继续之前完成。
|
||||
|
||||
`StructuredTaskScope` 的基本用法如下:
|
||||
|
||||
```java
|
||||
try (var scope = new StructuredTaskScope<Object>()) {
|
||||
// 使用fork方法派生线程来执行子任务
|
||||
Future<Integer> future1 = scope.fork(task1);
|
||||
Future<String> future2 = scope.fork(task2);
|
||||
// 等待线程完成
|
||||
scope.join();
|
||||
// 结果的处理可能包括处理或重新抛出异常
|
||||
... process results/exceptions ...
|
||||
} // close
|
||||
```
|
||||
|
||||
结构化并发非常适合虚拟线程,虚拟线程是 JDK 实现的轻量级线程。许多虚拟线程共享同一个操作系统线程,从而允许非常多的虚拟线程。
|
||||
|
||||
### JEP 481:作用域值(第三次预览)
|
||||
|
||||
作用域值(Scoped Values)可以在线程内和线程间共享不可变的数据,优于线程局部变量,尤其是在使用大量虚拟线程时。
|
||||
|
||||
```java
|
||||
final static ScopedValue<...> V = new ScopedValue<>();
|
||||
|
||||
// In some method
|
||||
ScopedValue.where(V, <value>)
|
||||
.run(() -> { ... V.get() ... call methods ... });
|
||||
|
||||
// In a method called directly or indirectly from the lambda expression
|
||||
... V.get() ...
|
||||
```
|
||||
|
||||
作用域值允许在大型程序中的组件之间安全有效地共享数据,而无需求助于方法参数。
|
||||
|
||||
### JEP 482:灵活的构造函数体(第二次预览)
|
||||
|
||||
这个特性最初在 JDK 22 由 [JEP 447: Statements before super(...) (Preview)](https://openjdk.org/jeps/447)提出。
|
||||
|
||||
Java 要求在构造函数中,`super(...)` 或 `this(...)` 调用必须作为第一条语句出现。这意味着我们无法在调用父类构造函数之前在子类构造函数中直接初始化字段。
|
||||
|
||||
灵活的构造函数体解决了这一问题,它允许在构造函数体内,在调用 `super(..)` 或 `this(..)` 之前编写语句,这些语句可以初始化字段,但不能引用正在构造的实例。这样可以防止在父类构造函数中调用子类方法时,子类的字段未被正确初始化,增强了类构造的可靠性。
|
||||
|
||||
这一特性解决了之前 Java 语法限制了构造函数代码组织的问题,让开发者能够更自由、更自然地表达构造函数的行为,例如在构造函数中直接进行参数验证、准备和共享,而无需依赖辅助方法或构造函数,提高了代码的可读性和可维护性。
|
||||
|
||||
```java
|
||||
class Person {
|
||||
private final String name;
|
||||
private int age;
|
||||
|
||||
public Person(String name, int age) {
|
||||
if (age < 0) {
|
||||
throw new IllegalArgumentException("Age cannot be negative.");
|
||||
}
|
||||
this.name = name; // 在调用父类构造函数之前初始化字段
|
||||
this.age = age;
|
||||
// ... 其他初始化代码
|
||||
}
|
||||
}
|
||||
|
||||
class Employee extends Person {
|
||||
private final int employeeId;
|
||||
|
||||
public Employee(String name, int age, int employeeId) {
|
||||
this.employeeId = employeeId; // 在调用父类构造函数之前初始化字段
|
||||
super(name, age); // 调用父类构造函数
|
||||
// ... 其他初始化代码
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## JDK 22
|
||||
|
||||
### JEP 423:G1 垃圾收集器区域固定
|
||||
|
||||
JEP 423 提出在 G1 垃圾收集器中实现区域固定(Region Pinning)功能,旨在减少由于 Java Native Interface (JNI) 关键区域导致的延迟问题。
|
||||
|
||||
JNI 关键区域内的对象不能在垃圾收集时被移动,因此 G1 以往通过禁用垃圾收集解决该问题,导致线程阻塞及严重的延迟。通过在 G1 的老年代和年轻代中引入区域固定机制,允许在关键区域内固定对象所在的内存区域,同时继续回收未固定的区域,避免了禁用垃圾回收的需求。这种改进有助于显著降低延迟,提升系统在与 JNI 交互时的吞吐量和稳定性。
|
||||
|
||||
### JEP 454:外部函数和内存 API
|
||||
|
||||
Java 程序可以通过该 API 与 Java 运行时之外的代码和数据进行互操作。通过高效地调用外部函数(即 JVM 之外的代码)和安全地访问外部内存(即不受 JVM 管理的内存),该 API 使 Java 程序能够调用本机库并处理本机数据,而不会像 JNI 那样危险和脆弱。
|
||||
|
||||
外部函数和内存 API 在 Java 17 中进行了第一轮孵化,由 [JEP 412](https://openjdk.java.net/jeps/412) 提出。Java 18 中进行了第二次孵化,由[JEP 419](https://openjdk.org/jeps/419) 提出。Java 19 中是第一次预览,由 [JEP 424](https://openjdk.org/jeps/424) 提出。JDK 20 中是第二次预览,由 [JEP 434](https://openjdk.org/jeps/434) 提出。JDK 21 中是第三次预览,由 [JEP 442](https://openjdk.org/jeps/442) 提出。
|
||||
|
||||
最终,该特性在 JDK 22 中顺利转正。
|
||||
|
||||
在 [Java 19 新特性概览](./java19.md) 中,我有详细介绍到外部函数和内存 API,这里就不再做额外的介绍了。
|
||||
|
||||
### JEP 456:未命名模式和变量
|
||||
|
||||
未命名模式和变量在 JDK 21 中由 [JEP 443](https://openjdk.org/jeps/443)提出预览,JDK 22 中就已经转正。
|
||||
|
||||
关于这个新特性的详细介绍,可以看看[Java 21 新特性概览(重要)](./java21.md)这篇文章中的介绍。
|
||||
|
||||
### JEP 458:启动多文件源代码程序
|
||||
|
||||
Java 11 引入了 [JEP 330:启动单文件源代码程序](https://openjdk.org/jeps/330),增强了 `java` 启动器的功能,使其能够直接运行单个 Java 源文件。通过命令 `java HelloWorld.java`,Java 可以在内存中隐式编译源代码并立即执行,而不需要在磁盘上生成 `.class` 文件。这简化了开发者在编写小型工具程序或学习 Java 时的工作流程,避免了手动编译的额外步骤。
|
||||
|
||||
假设文件 `Prog.java` 声明了两个类:
|
||||
|
||||
```java
|
||||
class Prog {
|
||||
public static void main(String[] args) { Helper.run(); }
|
||||
}
|
||||
|
||||
class Helper {
|
||||
static void run() { System.out.println("Hello!"); }
|
||||
}
|
||||
```
|
||||
|
||||
`java Prog.java` 命令会在内存中编译两个类并执行 `main` 该文件中声明的第一个类的方法。
|
||||
|
||||
这种方式有一个限制,程序的所有源代码必须放在一个 `.java` 文件中。
|
||||
|
||||
[JEP 458:启动多文件源代码程序](https://openjdk.org/jeps/458) 是对 JEP 330 功能的扩展,允许直接运行由多个 Java 源文件组成的程序,而无需显式的编译步骤。
|
||||
|
||||
假设一个目录中有两个 Java 源文件 `Prog.java` 和 `Helper.java`,每个文件各自声明了一个类:
|
||||
|
||||
```java
|
||||
// Prog.java
|
||||
class Prog {
|
||||
public static void main(String[] args) { Helper.run(); }
|
||||
}
|
||||
|
||||
// Helper.java
|
||||
class Helper {
|
||||
static void run() { System.out.println("Hello!"); }
|
||||
}
|
||||
```
|
||||
|
||||
当你运行命令 `java Prog.java` 时,Java 启动器会在内存中编译并执行 `Prog` 类的 `main` 方法。由于 `Prog` 类中的代码引用了 `Helper` 类,启动器会自动在文件系统中找到 `Helper.java` 文件,编译其中的 `Helper` 类,并在内存中执行它。这个过程是自动的,开发者无需显式调用 `javac` 来编译所有源文件。
|
||||
|
||||
这一特性使得从小型项目到大型项目的过渡更加平滑,开发者可以自由选择何时引入构建工具,避免在快速迭代时被迫设置复杂的项目结构。该特性消除了单文件的限制,进一步简化了从单一文件到多文件程序的开发过程,特别适合原型开发、快速实验以及早期项目的探索阶段。
|
||||
@@ -0,0 +1,266 @@
|
||||
---
|
||||
title: Java 24 新特性概览
|
||||
description: 总结 JDK 24 的新特性与改动,便于跟踪 Java 演进。
|
||||
category: Java
|
||||
tag:
|
||||
- Java新特性
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java 24,JDK24,JEP 更新,语言特性,GC 改进,平台增强
|
||||
---
|
||||
|
||||
JDK 24 于 2025 年 3 月发布,这是一个非 LTS(长期支持版)版本。下一个长期支持版是 **JDK 25**,预计于 2025 年 9 月发布。
|
||||
|
||||
JDK 24 共有 24 个新特性,这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||||
|
||||
- [JEP 478: Key Derivation Function API (密钥派生函数 API)](https://openjdk.org/jeps/478)
|
||||
- [JEP 483: Early Class-File Loading & Linking (提前类加载和链接)](https://openjdk.org/jeps/483)
|
||||
- [JEP 484: Class File API (类文件 API)](https://openjdk.org/jeps/484)
|
||||
- [JEP 485: Stream Gatherers (流收集器)](https://openjdk.org/jeps/485)
|
||||
- [JEP 486: Disable the Security Manager (永久禁用安全管理器)](https://openjdk.org/jeps/486)
|
||||
- [JEP 487: Scoped Values (作用域值, 第四次预览)](https://openjdk.org/jeps/487)
|
||||
- [JEP 495: Simplified Source Files and Instance Main Methods (简化的源文件和实例主方法, 第四次预览)](https://openjdk.org/jeps/495)
|
||||
- [JEP 497: Quantum-Resistant Digital Signature Algorithm (ML-DSA) (量子抗性数字签名算法)](https://openjdk.org/jeps/497)
|
||||
- [JEP 499: Structured Concurrency (结构化并发, 第四次预览)](https://openjdk.org/jeps/499)
|
||||
|
||||
下图是从 JDK 8 到 JDK 25 每个版本的更新带来的新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
## JEP 478: Key Derivation Function API(密钥派生函数 API)
|
||||
|
||||
密钥派生函数 API 是一种用于从初始密钥和其他数据派生额外密钥的加密算法。它的核心作用是为不同的加密目的(如加密、认证等)生成多个不同的密钥,避免密钥重复使用带来的安全隐患。这在新代加密中是一个重要的里程碑,为后续新兴的量子计算环境打下了基础。
|
||||
|
||||
通过该 API,开发者可以使用最新的密钥派生算法(如 HKDF 和未来的 Argon2):
|
||||
|
||||
```java
|
||||
// 创建一个 KDF 对象,使用 HKDF-SHA256 算法
|
||||
KDF hkdf = KDF.getInstance("HKDF-SHA256");
|
||||
|
||||
// 创建 Extract 和 Expand 参数规范
|
||||
AlgorithmParameterSpec params =
|
||||
HKDFParameterSpec.ofExtract()
|
||||
.addIKM(initialKeyMaterial) // 设置初始密钥材料
|
||||
.addSalt(salt) // 设置盐值
|
||||
.thenExpand(info, 32); // 设置扩展信息和目标长度
|
||||
|
||||
// 派生一个 32 字节的 AES 密钥
|
||||
SecretKey key = hkdf.deriveKey("AES", params);
|
||||
|
||||
// 可以使用相同的 KDF 对象进行其他密钥派生操作
|
||||
```
|
||||
|
||||
## JEP 483: Early Class-File Loading & Linking(提前类加载和链接)
|
||||
|
||||
在传统 JVM 中,应用在每次启动时需要动态加载和链接类。这种机制对启动时间敏感的应用(如微服务或无服务器函数)带来了显著的性能瓶颈。该特性通过缓存已加载和链接的类,显著减少了重复工作的开销,显著减少 Java 应用程序的启动时间。测试表明,对大型应用(如基于 Spring 的服务器应用),启动时间可减少 40% 以上。
|
||||
|
||||
这个优化是零侵入性的,对应用程序、库或框架的代码无需任何更改,启动也方式保持一致,仅需添加相关 JVM 参数(如 `-XX:+ClassDataSharing`)。
|
||||
|
||||
## JEP 484: Class File API(类文件 API)
|
||||
|
||||
类文件 API 在 JDK 22 进行了第一次预览([JEP 457](https://openjdk.org/jeps/457)),在 JDK 23 进行了第二次预览并进一步完善([JEP 466](https://openjdk.org/jeps/466))。最终,该特性在 JDK 24 中顺利转正。
|
||||
|
||||
类文件 API 的目标是提供一套标准化的 API,用于解析、生成和转换 Java 类文件,取代过去对第三方库(如 ASM)在类文件处理上的依赖。
|
||||
|
||||
```java
|
||||
// 创建一个 ClassFile 对象,这是操作类文件的入口。
|
||||
ClassFile cf = ClassFile.of();
|
||||
// 解析字节数组为 ClassModel
|
||||
ClassModel classModel = cf.parse(bytes);
|
||||
|
||||
// 构建新的类文件,移除以 "debug" 开头的所有方法
|
||||
byte[] newBytes = cf.build(classModel.thisClass().asSymbol(),
|
||||
classBuilder -> {
|
||||
// 遍历所有类元素
|
||||
for (ClassElement ce : classModel) {
|
||||
// 判断是否为方法 且 方法名以 "debug" 开头
|
||||
if (!(ce instanceof MethodModel mm
|
||||
&& mm.methodName().stringValue().startsWith("debug"))) {
|
||||
// 添加到新的类文件中
|
||||
classBuilder.with(ce);
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## JEP 485: Stream Gatherers(流收集器)
|
||||
|
||||
流收集器 `Stream::gather(Gatherer)` 是一个强大的新特性,它允许开发者定义自定义的中间操作,从而实现更复杂、更灵活的数据转换。`Gatherer` 接口是该特性的核心,它定义了如何从流中收集元素,维护中间状态,并在处理过程中生成结果。
|
||||
|
||||
与现有的 `filter`、`map` 或 `distinct` 等内置操作不同,`Stream::gather` 使得开发者能够实现那些难以用标准 Stream 操作完成的任务。例如,可以使用 `Stream::gather` 实现滑动窗口、自定义规则的去重、或者更复杂的状态转换和聚合。 这种灵活性极大地扩展了 Stream API 的应用范围,使开发者能够应对更复杂的数据处理场景。
|
||||
|
||||
基于 `Stream::gather(Gatherer)` 实现字符串长度的去重逻辑:
|
||||
|
||||
```java
|
||||
var result = Stream.of("foo", "bar", "baz", "quux")
|
||||
.gather(Gatherer.ofSequential(
|
||||
HashSet::new, // 初始化状态为 HashSet,用于保存已经遇到过的字符串长度
|
||||
(set, str, downstream) -> {
|
||||
if (set.add(str.length())) {
|
||||
return downstream.push(str);
|
||||
}
|
||||
return true; // 继续处理流
|
||||
}
|
||||
))
|
||||
.toList();// 转换为列表
|
||||
|
||||
// 输出结果 ==> [foo, quux]
|
||||
```
|
||||
|
||||
## JEP 486: Disable the Security Manager(永久禁用安全管理器)
|
||||
|
||||
JDK 24 不再允许启用 `Security Manager`,即使通过 `java -Djava.security.manager` 命令也无法启用,这是逐步移除该功能的关键一步。虽然 `Security Manager` 曾经是 Java 中限制代码权限(如访问文件系统或网络、读取或写入敏感文件、执行系统命令)的重要工具,但由于复杂性高、使用率低且维护成本大,Java 社区决定最终移除它。
|
||||
|
||||
## JEP 487: Scoped Values(作用域值, 第四次预览)
|
||||
|
||||
作用域值(Scoped Values)可以在线程内和线程间共享不可变的数据,优于线程局部变量,尤其是在使用大量虚拟线程时。
|
||||
|
||||
```java
|
||||
final static ScopedValue<...> V = new ScopedValue<>();
|
||||
|
||||
// In some method
|
||||
ScopedValue.where(V, <value>)
|
||||
.run(() -> { ... V.get() ... call methods ... });
|
||||
|
||||
// In a method called directly or indirectly from the lambda expression
|
||||
... V.get() ...
|
||||
```
|
||||
|
||||
作用域值允许在大型程序中的组件之间安全有效地共享数据,而无需求助于方法参数。
|
||||
|
||||
## JEP 491: Virtual Threads Synchronization Without Pinning(虚拟线程的同步而不固定平台线程)
|
||||
|
||||
优化了虚拟线程与 `synchronized` 的工作机制。 虚拟线程在 `synchronized` 方法和代码块中阻塞时,通常能够释放其占用的操作系统线程(平台线程),避免了对平台线程的长时间占用,从而提升应用程序的并发能力。 这种机制避免了“固定 (Pinning)”——即虚拟线程长时间占用平台线程,阻止其服务于其他虚拟线程的情况。
|
||||
|
||||
现有的使用 `synchronized` 的 Java 代码无需修改即可受益于虚拟线程的扩展能力。 例如,一个 I/O 密集型的应用程序,如果使用传统的平台线程,可能会因为线程阻塞而导致并发能力下降。 而使用虚拟线程,即使在 `synchronized` 块中发生阻塞,也不会固定平台线程,从而允许平台线程继续服务于其他虚拟线程,提高整体的并发性能。
|
||||
|
||||
## JEP 493: Linking Run-Time Images Without JMOD Files(在没有 JMOD 文件的情况下链接运行时镜像)
|
||||
|
||||
默认情况下,JDK 同时包含运行时镜像(运行时所需的模块)和 JMOD 文件。这个特性使得 jlink 工具无需使用 JDK 的 JMOD 文件就可以创建自定义运行时镜像,减少了 JDK 的安装体积(约 25%)。
|
||||
|
||||
说明:
|
||||
|
||||
- Jlink 是随 Java 9 一起发布的新命令行工具。它允许开发人员为基于模块的 Java 应用程序创建自己的轻量级、定制的 JRE。
|
||||
- JMOD 文件是 Java 模块的描述文件,包含了模块的元数据和资源。
|
||||
|
||||
## JEP 495: Simplified Source Files and Instance Main Methods(简化的源文件和实例主方法, 第四次预览)
|
||||
|
||||
这个特性主要简化了 `main` 方法的声明。对于 Java 初学者来说,这个 `main` 方法的声明引入了太多的 Java 语法概念,不利于初学者快速上手。
|
||||
|
||||
没有使用该特性之前定义一个 `main` 方法:
|
||||
|
||||
```java
|
||||
public class HelloWorld {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, World!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
使用该新特性之后定义一个 `main` 方法:
|
||||
|
||||
```java
|
||||
class HelloWorld {
|
||||
void main() {
|
||||
System.out.println("Hello, World!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
进一步简化(未命名的类允许我们省略类名)
|
||||
|
||||
```java
|
||||
void main() {
|
||||
System.out.println("Hello, World!");
|
||||
}
|
||||
```
|
||||
|
||||
## JEP 497: Quantum-Resistant Digital Signature Algorithm (ML-DSA)(量子抗性数字签名算法)
|
||||
|
||||
JDK 24 引入了支持实施抗量子的基于模块晶格的数字签名算法(Module-Lattice-Based Digital Signature Algorithm, **ML-DSA**),为抵御未来量子计算机可能带来的威胁做准备。
|
||||
|
||||
ML-DSA 是美国国家标准与技术研究院(NIST)在 FIPS 204 中标准化的量子抗性算法,用于数字签名和身份验证。
|
||||
|
||||
## JEP 498: Warnings When Using `sun.misc.Unsafe` Memory Access Methods (使用 `sun.misc.Unsafe` 内存访问方法时发出警告)
|
||||
|
||||
JDK 23([JEP 471](https://openjdk.org/jeps/471)) 提议弃用 `sun.misc.Unsafe` 中的内存访问方法,这些方法将来的版本中会被移除。在 JDK 24 中,当首次调用 `sun.misc.Unsafe` 的任何内存访问方法时,运行时会发出警告。
|
||||
|
||||
这些不安全的方法已有安全高效的替代方案:
|
||||
|
||||
- `java.lang.invoke.VarHandle`:JDK 9 (JEP 193) 中引入,提供了一种安全有效地操作堆内存的方法,包括对象的字段、类的静态字段以及数组元素。
|
||||
- `java.lang.foreign.MemorySegment`:JDK 22 (JEP 454) 中引入,提供了一种安全有效地访问堆外内存的方法,有时会与 `VarHandle` 协同工作。
|
||||
|
||||
这两个类是 Foreign Function & Memory API(外部函数和内存 API) 的核心组件,分别用于管理和操作堆外内存。Foreign Function & Memory API 在 JDK 22 中正式转正,成为标准特性。
|
||||
|
||||
```java
|
||||
import jdk.incubator.foreign.*;
|
||||
import java.lang.invoke.VarHandle;
|
||||
|
||||
// 管理堆外整数数组的类
|
||||
class OffHeapIntBuffer {
|
||||
|
||||
// 用于访问整数元素的VarHandle
|
||||
private static final VarHandle ELEM_VH = ValueLayout.JAVA_INT.arrayElementVarHandle();
|
||||
|
||||
// 内存管理器
|
||||
private final Arena arena;
|
||||
|
||||
// 堆外内存段
|
||||
private final MemorySegment buffer;
|
||||
|
||||
// 构造函数,分配指定数量的整数空间
|
||||
public OffHeapIntBuffer(long size) {
|
||||
this.arena = Arena.ofShared();
|
||||
this.buffer = arena.allocate(ValueLayout.JAVA_INT, size);
|
||||
}
|
||||
|
||||
// 释放内存
|
||||
public void deallocate() {
|
||||
arena.close();
|
||||
}
|
||||
|
||||
// 以volatile方式设置指定索引的值
|
||||
public void setVolatile(long index, int value) {
|
||||
ELEM_VH.setVolatile(buffer, 0L, index, value);
|
||||
}
|
||||
|
||||
// 初始化指定范围的元素为0
|
||||
public void initialize(long start, long n) {
|
||||
buffer.asSlice(ValueLayout.JAVA_INT.byteSize() * start,
|
||||
ValueLayout.JAVA_INT.byteSize() * n)
|
||||
.fill((byte) 0);
|
||||
}
|
||||
|
||||
// 将指定范围的元素复制到新数组
|
||||
public int[] copyToNewArray(long start, int n) {
|
||||
return buffer.asSlice(ValueLayout.JAVA_INT.byteSize() * start,
|
||||
ValueLayout.JAVA_INT.byteSize() * n)
|
||||
.toArray(ValueLayout.JAVA_INT);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## JEP 499: Structured Concurrency(结构化并发, 第四次预览)
|
||||
|
||||
JDK 19 引入了结构化并发,一种多线程编程方法,目的是为了通过结构化并发 API 来简化多线程编程,并不是为了取代 `java.util.concurrent`,目前处于孵化器阶段。
|
||||
|
||||
结构化并发将不同线程中运行的多个任务视为单个工作单元,从而简化错误处理、提高可靠性并增强可观察性。也就是说,结构化并发保留了单线程代码的可读性、可维护性和可观察性。
|
||||
|
||||
结构化并发的基本 API 是 `StructuredTaskScope`,它支持将任务拆分为多个并发子任务,在它们自己的线程中执行,并且子任务必须在主任务继续之前完成。
|
||||
|
||||
`StructuredTaskScope` 的基本用法如下:
|
||||
|
||||
```java
|
||||
try (var scope = new StructuredTaskScope<Object>()) {
|
||||
// 使用fork方法派生线程来执行子任务
|
||||
Future<Integer> future1 = scope.fork(task1);
|
||||
Future<String> future2 = scope.fork(task2);
|
||||
// 等待线程完成
|
||||
scope.join();
|
||||
// 结果的处理可能包括处理或重新抛出异常
|
||||
... process results/exceptions ...
|
||||
} // close
|
||||
```
|
||||
|
||||
结构化并发非常适合虚拟线程,虚拟线程是 JDK 实现的轻量级线程。许多虚拟线程共享同一个操作系统线程,从而允许非常多的虚拟线程。
|
||||
@@ -0,0 +1,241 @@
|
||||
---
|
||||
title: Java 25 新特性概览
|
||||
description: 概览 JDK 25 的关键新特性与预览改动,关注并发、GC 与语言/平台增强。
|
||||
category: Java
|
||||
tag:
|
||||
- Java新特性
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java 25,JDK25,LTS,作用域值,紧凑对象头,分代 Shenandoah,模块导入,结构化并发
|
||||
---
|
||||
|
||||
JDK 25 于 2025 年 9 月 16 日 发布,这是一个非常重要的版本,里程碑式。
|
||||
|
||||
JDK 25 是 LTS(长期支持版),至此为止,目前有 JDK8、JDK11、JDK17、JDK21 和 JDK 25 这五个长期支持版了。
|
||||
|
||||
JDK 25 共有 18 个新特性,这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||||
|
||||
- [JEP 506: Scoped Values (作用域值)](https://openjdk.org/jeps/506)
|
||||
- [JEP 512: Compact Source Files and Instance Main Methods (紧凑源文件与实例主方法)](https://openjdk.org/jeps/512)
|
||||
- [JEP 519: Compact Object Headers (紧凑对象头)](https://openjdk.org/jeps/519)
|
||||
- [JEP 521: Generational Shenandoah (分代 Shenandoah GC)](https://openjdk.org/jeps/521)
|
||||
- [JEP 507: Primitive Types in Patterns, instanceof, and switch (模式匹配支持基本类型, 第三次预览)](https://openjdk.org/jeps/507)
|
||||
- [JEP 505: Structured Concurrency (结构化并发, 第五次预览)](https://openjdk.org/jeps/505)
|
||||
- [JEP 511: Module Import Declarations (模块导入声明)](https://openjdk.org/jeps/511)
|
||||
- [JEP 513: Flexible Constructor Bodies (灵活的构造函数体)](https://openjdk.org/jeps/513)
|
||||
- [JEP 508: Vector API (向量 API, 第十次孵化)](https://openjdk.org/jeps/508)
|
||||
|
||||
下图是从 JDK 8 到 JDK 25 每个版本的更新带来的新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
## JDK 25
|
||||
|
||||
### JEP 506: 作用域值
|
||||
|
||||
作用域值(Scoped Values)可以在线程内和线程间共享不可变的数据,优于线程局部变量 `ThreadLocal`,尤其是在使用大量虚拟线程时。
|
||||
|
||||
```java
|
||||
final static ScopedValue<...> V = new ScopedValue<>();
|
||||
|
||||
// In some method
|
||||
ScopedValue.where(V, <value>)
|
||||
.run(() -> { ... V.get() ... call methods ... });
|
||||
|
||||
// In a method called directly or indirectly from the lambda expression
|
||||
... V.get() ...
|
||||
```
|
||||
|
||||
作用域值通过其“写入时复制”(copy-on-write)的特性,保证了数据在线程间的隔离与安全,同时性能极高,占用内存也极低。这个特性将成为未来 Java 并发编程的标准实践。
|
||||
|
||||
### JEP 512: 紧凑源文件与实例主方法
|
||||
|
||||
该特性第一次预览是由 [JEP 445](https://openjdk.org/jeps/445 "JEP 445")(JDK 21)提出,随后经过了 JDK 22、JDK 23 和 JDK 24 的改进和完善,最终在 JDK 25 顺利转正。
|
||||
|
||||
这个改进极大地简化了编写简单 Java 程序的步骤,允许将类和主方法写在同一个没有顶级 `public class` 的文件中,并允许 `main` 方法成为一个非静态的实例方法。
|
||||
|
||||
```java
|
||||
class HelloWorld {
|
||||
void main() {
|
||||
System.out.println("Hello, World!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
进一步简化:
|
||||
|
||||
```java
|
||||
void main() {
|
||||
System.out.println("Hello, World!");
|
||||
}
|
||||
```
|
||||
|
||||
这是为了降低 Java 的学习门槛和提升编写小型程序、脚本的效率而迈出的一大步。初学者不再需要理解 `public static void main(String[] args)` 这一长串复杂的声明。对于快速原型验证和脚本编写,这也使得 Java 成为一个更有吸引力的选择。
|
||||
|
||||
### JEP 519: 紧凑对象头
|
||||
|
||||
该特性第一次预览是由 [JEP 450](https://openjdk.org/jeps/450 "JEP 450")(JDK 24)提出,JDK 25 就顺利转正了。
|
||||
|
||||
通过优化对象头的内部结构,在 64 位架构的 HotSpot 虚拟机中,将对象头大小从原本的 96-128 位(12-16 字节)缩减至 64 位(8 字节),最终实现减少堆内存占用、提升部署密度、增强数据局部性的效果。
|
||||
|
||||
紧凑对象头并没有成为 JVM 默认的对象头布局方式,需通过显式配置启用:
|
||||
|
||||
- JDK 24 需通过命令行参数组合启用:
|
||||
`$ java -XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders ...`;
|
||||
- JDK 25 之后仅需 `-XX:+UseCompactObjectHeaders` 即可启用。
|
||||
|
||||
### JEP 521: 分代 Shenandoah GC
|
||||
|
||||
Shenandoah GC 在 JDK12 中成为正式可生产使用的 GC,默认关闭,通过 `-XX:+UseShenandoahGC` 启用。
|
||||
|
||||
Redhat 主导开发的 Pauseless GC 实现,主要目标是 99.9% 的暂停小于 10ms,暂停与堆大小无关等
|
||||
|
||||
传统的 Shenandoah 对整个堆进行并发标记和整理,虽然暂停时间极短,但在处理年轻代对象时效率不如分代 GC。引入分代后,Shenandoah 可以更频繁、更高效地回收年轻代中的大量“朝生夕死”的对象,使其在保持极低暂停时间的同时,拥有了更高的吞吐量和更低的 CPU 开销。
|
||||
|
||||
Shenandoah GC 需要通过命令启用:
|
||||
|
||||
- JDK 24 需通过命令行参数组合启用:`-XX:+UseShenandoahGC -XX:+UnlockExperimentalVMOptions -XX:ShenandoahGCMode=generational`
|
||||
- JDK 25 之后仅需 `-XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational` 即可启用。
|
||||
|
||||
### JEP 507: 模式匹配支持基本类型(第三次预览)
|
||||
|
||||
该特性第一次预览是由 [JEP 455](https://openjdk.org/jeps/455 "JEP 455")(JDK 23)提出。
|
||||
|
||||
模式匹配可以在 `switch` 和 `instanceof` 语句中处理所有的基本数据类型(`int`, `double`, `boolean` 等)
|
||||
|
||||
```java
|
||||
static void test(Object obj) {
|
||||
if (obj instanceof int i) {
|
||||
System.out.println("这是一个int类型: " + i);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这样就可以像处理对象类型一样,对基本类型进行更安全、更简洁的类型匹配和转换,进一步消除了 Java 中的模板代码。
|
||||
|
||||
### JEP 505: 结构化并发(第五次预览)
|
||||
|
||||
JDK 19 引入了结构化并发,一种多线程编程方法,目的是为了通过结构化并发 API 来简化多线程编程,并不是为了取代 `java.util.concurrent`,目前处于孵化器阶段。
|
||||
|
||||
结构化并发将不同线程中运行的多个任务视为单个工作单元,从而简化错误处理、提高可靠性并增强可观察性。也就是说,结构化并发保留了单线程代码的可读性、可维护性和可观察性。
|
||||
|
||||
结构化并发的基本 API 是 `StructuredTaskScope`,它支持将任务拆分为多个并发子任务,在它们自己的线程中执行,并且子任务必须在主任务继续之前完成。
|
||||
|
||||
`StructuredTaskScope` 的基本用法如下:
|
||||
|
||||
```java
|
||||
try (var scope = new StructuredTaskScope<Object>()) {
|
||||
// 使用fork方法派生线程来执行子任务
|
||||
Future<Integer> future1 = scope.fork(task1);
|
||||
Future<String> future2 = scope.fork(task2);
|
||||
// 等待线程完成
|
||||
scope.join();
|
||||
// 结果的处理可能包括处理或重新抛出异常
|
||||
... process results/exceptions ...
|
||||
} // close
|
||||
```
|
||||
|
||||
结构化并发非常适合虚拟线程,虚拟线程是 JDK 实现的轻量级线程。许多虚拟线程共享同一个操作系统线程,从而允许非常多的虚拟线程。
|
||||
|
||||
### JEP 511: 模块导入声明
|
||||
|
||||
该特性第一次预览是由 [JEP 476](https://openjdk.org/jeps/476 "JEP 476")(JDK 23)提出,随后在 [JEP 494](https://openjdk.org/jeps/494 "JEP 494")(JDK 24)中进行了完善,JDK 25 顺利转正。
|
||||
|
||||
模块导入声明允许在 Java 代码中简洁地导入整个模块的所有导出包,而无需逐个声明包的导入。这一特性简化了模块化库的重用,特别是在使用多个模块时,避免了大量的包导入声明,使得开发者可以更方便地访问第三方库和 Java 基本类。
|
||||
|
||||
此特性对初学者和原型开发尤为有用,因为它无需开发者将自己的代码模块化,同时保留了对传统导入方式的兼容性,提升了开发效率和代码可读性。
|
||||
|
||||
```java
|
||||
// 导入整个 java.base 模块,开发者可以直接访问 List、Map、Stream 等类,而无需每次手动导入相关包
|
||||
import module java.base;
|
||||
|
||||
public class Example {
|
||||
public static void main(String[] args) {
|
||||
String[] fruits = { "apple", "berry", "citrus" };
|
||||
Map<String, String> fruitMap = Stream.of(fruits)
|
||||
.collect(Collectors.toMap(
|
||||
s -> s.toUpperCase().substring(0, 1),
|
||||
Function.identity()));
|
||||
|
||||
System.out.println(fruitMap);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### JEP 513: 灵活的构造函数体
|
||||
|
||||
该特性第一次预览是由 [JEP 447](https://openjdk.org/jeps/447 "JEP 447")(JDK 22)提出,随后在 [JEP 482 ](https://openjdk.org/jeps/482 "JEP 482 ")(JDK 23)和 [JEP 492](https://openjdk.org/jeps/492 "JEP 492")(JDK 24)经历了预览,JDK 25 顺利转正。
|
||||
|
||||
Java 要求在构造函数中,`super(...)` 或 `this(...)` 调用必须作为第一条语句出现。这意味着我们无法在调用父类构造函数之前在子类构造函数中直接初始化字段。
|
||||
|
||||
灵活的构造函数体解决了这一问题,它允许在构造函数体内,在调用 `super(..)` 或 `this(..)` 之前编写语句,这些语句可以初始化字段,但不能引用正在构造的实例。这样可以防止在父类构造函数中调用子类方法时,子类的字段未被正确初始化,增强了类构造的可靠性。
|
||||
|
||||
这一特性解决了之前 Java 语法限制了构造函数代码组织的问题,让开发者能够更自由、更自然地表达构造函数的行为,例如在构造函数中直接进行参数验证、准备和共享,而无需依赖辅助方法或构造函数,提高了代码的可读性和可维护性。
|
||||
|
||||
```java
|
||||
class Person {
|
||||
private final String name;
|
||||
private int age;
|
||||
|
||||
public Person(String name, int age) {
|
||||
if (age < 0) {
|
||||
throw new IllegalArgumentException("Age cannot be negative.");
|
||||
}
|
||||
this.name = name; // 在调用父类构造函数之前初始化字段
|
||||
this.age = age;
|
||||
// ... 其他初始化代码
|
||||
}
|
||||
}
|
||||
|
||||
class Employee extends Person {
|
||||
private final int employeeId;
|
||||
|
||||
public Employee(String name, int age, int employeeId) {
|
||||
this.employeeId = employeeId; // 在调用父类构造函数之前初始化字段
|
||||
super(name, age); // 调用父类构造函数
|
||||
// ... 其他初始化代码
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### JEP 508: 向量 API(第十次孵化)
|
||||
|
||||
向量计算由对向量的一系列操作组成。向量 API 用来表达向量计算,该计算可以在运行时可靠地编译为支持的 CPU 架构上的最佳向量指令,从而实现优于等效标量计算的性能。
|
||||
|
||||
向量 API 的目标是为用户提供简洁易用且与平台无关的表达范围广泛的向量计算。
|
||||
|
||||
这是对数组元素的简单标量计算:
|
||||
|
||||
```java
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这是使用 Vector API 进行的等效向量计算:
|
||||
|
||||
```java
|
||||
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
|
||||
|
||||
void vectorComputation(float[] a, float[] b, float[] c) {
|
||||
int i = 0;
|
||||
int upperBound = SPECIES.loopBound(a.length);
|
||||
for (; i < upperBound; i += SPECIES.length()) {
|
||||
// FloatVector va, vb, vc;
|
||||
var va = FloatVector.fromArray(SPECIES, a, i);
|
||||
var vb = FloatVector.fromArray(SPECIES, b, i);
|
||||
var vc = va.mul(va)
|
||||
.add(vb.mul(vb))
|
||||
.neg();
|
||||
vc.intoArray(c, i);
|
||||
}
|
||||
for (; i < a.length; i++) {
|
||||
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
尽管仍在孵化中,但其第十次迭代足以证明其重要性。它使得 Java 在科学计算、机器学习、大数据处理等性能敏感领域,能够编写出接近甚至媲美 C++等本地语言性能的代码。这是 Java 在高性能计算领域保持竞争力的关键。
|
||||
@@ -0,0 +1,324 @@
|
||||
---
|
||||
title: Java 26 新特性概览
|
||||
description: 概览 JDK 26 的关键新特性与预览改动,关注 HTTP/3、GC 性能优化、AOT 缓存与语言/平台增强。
|
||||
category: Java
|
||||
tag:
|
||||
- Java新特性
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java 26,JDK26,HTTP/3,G1 GC,AOT 缓存,延迟常量,结构化并发,向量 API,模式匹配
|
||||
---
|
||||
|
||||
JDK 26 于 2026 年 3 月 17 日 发布,这是一个非 LTS(非长期支持版)版本。上一个长期支持版是 **JDK 25**,下一个长期支持版预计是 **JDK 29**。
|
||||
|
||||
JDK 26 共有 10 个新特性,这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||||
|
||||
- [JEP 517: HTTP/3 for the HTTP Client API (为 HTTP Client API 引入 HTTP/3 支持)](https://openjdk.org/jeps/517)
|
||||
- [JEP 522: G1 GC: Improve Throughput by Reducing Synchronization (G1 GC 吞吐量优化)](https://openjdk.org/jeps/522)
|
||||
- [JEP 516: Ahead-of-Time Object Caching with Any GC (AOT 对象缓存支持任意 GC)](https://openjdk.org/jeps/516)
|
||||
- [JEP 500: Prepare to Make Final Mean Final (准备让 final 真正不可变)](https://openjdk.org/jeps/500)
|
||||
- [JEP 526: Lazy Constants (延迟常量, 第二次预览)](https://openjdk.org/jeps/526)
|
||||
- [JEP 525: Structured Concurrency (结构化并发, 第六次预览)](https://openjdk.org/jeps/525)
|
||||
- [JEP 530: Primitive Types in Patterns, instanceof, and switch (模式匹配支持基本类型, 第四次预览)](https://openjdk.org/jeps/530)
|
||||
- [JEP 524: PEM Encodings of Cryptographic Objects (加密对象 PEM 编码, 第二次预览)](https://openjdk.org/jeps/524)
|
||||
- [JEP 529: Vector API (向量 API, 第十一次孵化)](https://openjdk.org/jeps/529)
|
||||
- [JEP 504: Remove the Applet API (移除 Applet API)](https://openjdk.org/jeps/504)
|
||||
|
||||
下图是从 JDK 8 到 JDK 25 每个版本的更新带来的新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
## JEP 517: 为 HTTP Client API 引入 HTTP/3 支持
|
||||
|
||||
JDK 26 为 `java.net.http.HttpClient` API 正式添加了 **HTTP/3** 支持,这是一个期待已久的重要更新。
|
||||
|
||||
**HTTP/3 的优势**:
|
||||
|
||||
- **基于 QUIC 协议**:HTTP/2 是基于 TCP 协议实现的,HTTP/3 新增了 QUIC(Quick UDP Internet Connections) 协议来实现可靠的传输,提供与 TLS/SSL 相当的安全性,具有较低的连接和传输延迟。你可以将 QUIC 看作是 UDP 的升级版本,在其基础上新增了很多功能比如加密、重传等等。
|
||||
- **消除队头阻塞**:HTTP/2 多请求复用一个 TCP 连接,一旦发生丢包,就会阻塞住所有的 HTTP 请求。由于 QUIC 协议的特性,HTTP/3 在一定程度上解决了队头阻塞(Head-of-Line blocking, 简写:HOL blocking)问题,一个连接建立多个不同的数据流,这些数据流之间独立互不影响,某个数据流发生丢包了,其数据流不受影响(本质上是多路复用+轮询)。
|
||||
- **更快的连接建立**:HTTP/2 需要经过经典的 TCP 三次握手过程(由于安全的 HTTPS 连接建立还需要 TLS 握手,共需要大约 3 个 RTT)。由于 QUIC 协议的特性(TLS 1.3,TLS 1.3 除了支持 1 个 RTT 的握手,还支持 0 个 RTT 的握手)连接建立仅需 0-RTT 或者 1-RTT。这意味着 QUIC 在最佳情况下不需要任何的额外往返时间就可以建立新连接。
|
||||
- **更好的移动端体验**:HTTP/3.0 支持连接迁移,因为 QUIC 使用 64 位 ID 标识连接,只要 ID 不变就不会中断,网络环境改变时(如从 Wi-Fi 切换到移动数据)也能保持连接。而 TCP 连接是由(源 IP,源端口,目的 IP,目的端口)组成,这个四元组中一旦有一项值发生改变,这个连接也就不能用了。
|
||||
|
||||
详细介绍可以阅读这篇文章:[计算机网络常见面试题总结(上)](https://javaguide.cn/cs-basics/network/other-network-questions.html)(网络分层模型、常见网路协议总结、HTTP、WebSocket、DNS 等)
|
||||
|
||||
**使用方式**:
|
||||
|
||||
HTTP/3 的使用非常简单,几乎不需要修改现有代码。`HttpClient` 会自动协商使用最高版本的 HTTP 协议:
|
||||
|
||||
```java
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("https://example.com"))
|
||||
.build();
|
||||
|
||||
// 如果服务器支持 HTTP/3,HttpClient 会自动升级使用
|
||||
HttpResponse<String> response = client.send(request,
|
||||
HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
System.out.println(response.body());
|
||||
```
|
||||
|
||||
如果需要明确指定使用 HTTP/3,可以通过 `version()` 方法设置:
|
||||
|
||||
```java
|
||||
// 所有请求默认优先使用 HTTP/3
|
||||
HttpClient client = HttpClient.newBuilder()
|
||||
.version(HttpClient.Version.HTTP_3) // 明确指定 HTTP/3
|
||||
.build();
|
||||
|
||||
// 设置单个HttpRequest对象的首选协议版本
|
||||
HttpRequest request = HttpRequest.newBuilder(URI.create("https://javaguide.cn/"))
|
||||
.version(HttpClient.Version.HTTP_3)
|
||||
.GET().build();
|
||||
```
|
||||
|
||||
## JEP 522: G1 GC 吞吐量优化
|
||||
|
||||
**从 JDK9 开始,G1 垃圾收集器成为了默认的垃圾收集器。** 它在延迟和吞吐量之间寻求平衡。然而,这种平衡有时会影响应用程序的性能。与面向吞吐量的 Parallel GC 相比,G1 更多地与应用程序并发工作,以减少 GC 暂停时间。但这意味着应用线程必须与 GC 线程共享 CPU 并进行协调,这种同步会降低吞吐量并增加延迟。
|
||||
|
||||
JEP 522 引入了**双卡表(Card Table)**机制:
|
||||
|
||||
1. **第一张卡表**:应用线程的写屏障在更新这张卡表时**无需任何同步**,使得写屏障代码更简单、更快速。
|
||||
2. **第二张卡表**:优化器线程在后台并行处理这张初始为空的卡表。
|
||||
|
||||
当 G1 检测到扫描第一张卡表可能超过暂停时间目标时,它会原子性地交换这两张卡表。应用线程继续更新空的、原先的第二张表,而优化器线程则处理满的、原先的第一张表,无需进一步同步。
|
||||
|
||||
**性能提升效果**:
|
||||
|
||||
- 在**频繁修改对象引用字段**的应用中,吞吐量提升 **5-15%**
|
||||
- 即使在不频繁修改引用字段的应用中,由于写屏障简化(x64 上从约 50 条指令减少到仅 12 条),吞吐量也能提升高达 **5%**
|
||||
- GC 暂停时间也有**轻微下降**
|
||||
|
||||
**内存开销**:
|
||||
|
||||
第二张卡表与第一张容量相同,每张卡表需要 Java 堆容量的 0.2%,即每 1GB 堆内存额外使用约 2MB 原生内存。
|
||||
|
||||
## JEP 516: AOT 对象缓存支持任意 GC
|
||||
|
||||
这是 **Project Leyden** 的重要里程碑,使得提前(AOT)对象缓存能够与**任意垃圾收集器**配合使用。
|
||||
|
||||
之前在 JDK 24 中引入的 AOT 类数据共享(JEP 483)只支持 G1 垃圾收集器,无法与 ZGC 等其他 GC 配合使用。这是因为 AOT 缓存中存储的对象引用使用的是物理内存地址,而不同 GC 的内存布局和对象移动策略不同。
|
||||
|
||||
JEP 516 将对象引用的存储方式从**物理内存地址**改为**逻辑索引**:
|
||||
|
||||
- 使用 GC 无关的流式格式存储缓存
|
||||
- 缓存可以在运行时被任意 GC 加载和解析
|
||||
- JVM 在加载时将逻辑索引转换为实际的内存地址
|
||||
|
||||
**性能收益**:
|
||||
|
||||
- **启动时间优化**:显著减少 Java 应用的冷启动时间
|
||||
- **支持 ZGC**:低延迟的 ZGC 现在也能享受 AOT 缓存带来的启动加速
|
||||
- **云原生友好**:对于微服务和无服务器函数等启动时间敏感的场景特别有价值
|
||||
|
||||
## JEP 500: 准备让 final 真正不可变
|
||||
|
||||
这个特性为 Java 的完整性优先原则铺平道路,准备让 `final` 字段真正变得不可变。
|
||||
|
||||
从 JDK 1.0 开始,Java 的 `final` 字段实际上可以通过**深度反射**被修改:
|
||||
|
||||
```java
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
class Example {
|
||||
private final String name = "Original";
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
// 通过反射修改 final 字段
|
||||
Example example = new Example();
|
||||
Field field = Example.class.getDeclaredField("name");
|
||||
field.setAccessible(true);
|
||||
|
||||
// 移除 final 修饰符
|
||||
Field modifiersField = Field.class.getDeclaredField("modifiers");
|
||||
modifiersField.setAccessible(true);
|
||||
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
|
||||
|
||||
field.set(example, "Modified"); // 成功修改了 final 字段!
|
||||
System.out.println(example.getName()); // 输出 "Modified"
|
||||
```
|
||||
|
||||
这种能力虽然被一些框架(如序列化库、依赖注入框架、测试工具)使用,但破坏了 `final` 的不可变性保证,也阻碍了编译器优化。
|
||||
|
||||
在 JDK 26 中,当通过深度反射修改 `final` 字段时,JVM 会**发出警告**。这是为未来版本中默认禁止此类操作做准备。
|
||||
|
||||
对于确实需要修改 `final` 字段的场景,JDK 26 提供了显式的选择机制,允许开发者在过渡期继续使用此能力,同时为未来的严格模式做好准备。
|
||||
|
||||
## JEP 526: 延迟常量(第二次预览)
|
||||
|
||||
该特性第一次预览是由 [JEP 501](https://openjdk.org/jeps/501)(JDK 25)提出,JDK 26 是第二次预览。
|
||||
|
||||
传统的 `static final` 字段在类加载时就会初始化,这会:
|
||||
|
||||
- 增加启动时间。
|
||||
- 如果该常量从未被使用,则浪费内存。
|
||||
- 需要复杂的延迟初始化模式(如双重检查锁定、Holder 类模式等)。
|
||||
|
||||
JEP 526 引入了 `LazyConstant<T>`,一种持有不可变数据的对象,JVM 将其视为真正的常量,以获得与声明 `final` 字段相同的性能。
|
||||
|
||||
```java
|
||||
// 传统方式:类加载时立即初始化
|
||||
static final ExpensiveObject TRADITIONAL = new ExpensiveObject();
|
||||
|
||||
// 新方式:首次访问时才初始化
|
||||
static final LazyConstant<ExpensiveObject> LAZY =
|
||||
LazyConstant.of(() -> new ExpensiveObject());
|
||||
|
||||
// 使用时
|
||||
ExpensiveObject obj = LAZY.get(); // 此时才初始化
|
||||
```
|
||||
|
||||
**优势**:
|
||||
|
||||
- **按需初始化**:只在首次访问时初始化,提升启动性能。
|
||||
- **线程安全**:内置线程安全保证,无需手动同步。
|
||||
- **JVM 优化**:JVM 可以像对待 `final` 字段一样优化延迟常量。
|
||||
- **简化代码**:消除双重检查锁定等复杂的延迟初始化模式。
|
||||
|
||||
## JEP 525: 结构化并发(第六次预览)
|
||||
|
||||
JDK 19 引入了结构化并发,一种多线程编程方法,目的是为了通过结构化并发 API 来简化多线程编程,并不是为了取代 `java.util.concurrent`,目前处于孵化器阶段。
|
||||
|
||||
结构化并发将不同线程中运行的多个任务视为单个工作单元,从而简化错误处理、提高可靠性并增强可观察性。也就是说,结构化并发保留了单线程代码的可读性、可维护性和可观察性。
|
||||
|
||||
结构化并发的基本 API 是 `StructuredTaskScope`,它支持将任务拆分为多个并发子任务,在它们自己的线程中执行,并且子任务必须在主/父任务继续之前完成或者子任务随主/父任务失败而取消。
|
||||
|
||||
`StructuredTaskScope` 的基本用法如下:
|
||||
|
||||
```java
|
||||
try (var scope = new StructuredTaskScope<Object>()) {
|
||||
// 使用fork方法派生线程来执行子任务
|
||||
Future<Integer> future1 = scope.fork(task1);
|
||||
Future<String> future2 = scope.fork(task2);
|
||||
// 等待线程完成
|
||||
scope.join();
|
||||
// 结果的处理可能包括处理或重新抛出异常
|
||||
... process results/exceptions ...
|
||||
} // close
|
||||
```
|
||||
|
||||
结构化并发非常适合虚拟线程,虚拟线程是 JDK 实现的轻量级线程。许多虚拟线程共享同一个操作系统线程,从而允许非常多的虚拟线程。
|
||||
|
||||
**Java 26 的新变动**:
|
||||
|
||||
- **Joiner 增强**:`Joiner` 接口新增 `onTimeout()` 方法,允许在超时发生时返回特定结果。
|
||||
- **返回类型优化**:`allSuccessfulOrThrow()` 现在直接返回结果列表(`List`),而非之前的子任务流。
|
||||
- **API 简化**:将 `anySuccessfulResultOrThrow()` 简化更名为 `anySuccessfulOrThrow()`。
|
||||
|
||||
## JEP 530: 模式匹配支持基本类型(第四次预览)
|
||||
|
||||
该特性第一次预览是由 [JEP 455](https://openjdk.org/jeps/455 "JEP 455")(JDK 23)提出。
|
||||
|
||||
模式匹配可以在 `switch` 和 `instanceof` 语句中处理所有的基本数据类型(`int`, `double`, `boolean` 等)
|
||||
|
||||
```java
|
||||
static void test(Object obj) {
|
||||
if (obj instanceof int i) {
|
||||
System.out.println("这是一个int类型: " + i);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
JDK 26 对该特性进行了进一步增强:
|
||||
|
||||
- 消除了与基本类型相关的多项限制,使模式匹配、`instanceof` 和 `switch` 更加统一和表达力更强。
|
||||
- 增强了无条件精确性的定义。
|
||||
- 在 `switch` 构造中应用更严格的支配性检查,使编译器能够识别并减少更广泛的编码错误。
|
||||
|
||||
这样就可以像处理对象类型一样,对基本类型进行更安全、更简洁的类型匹配和转换,进一步消除了 Java 中的模板代码。
|
||||
|
||||
## JEP 524: 加密对象 PEM 编码(第二次预览)
|
||||
|
||||
该特性第一次预览是由 [JEP 518](https://openjdk.org/jeps/518)(JDK 25)提出。
|
||||
|
||||
PEM(Privacy-Enhanced Mail)是一种广泛使用的文本格式,用于存储和传输加密对象,如证书、私钥和公钥。JEP 524 提供了一个新的 API,用于将加密对象编码为 PEM 格式,以及从 PEM 格式解码回加密对象。
|
||||
|
||||
```java
|
||||
// 将密钥编码为 PEM 格式
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
|
||||
kpg.initialize(2048);
|
||||
KeyPair keyPair = kpg.generateKeyPair();
|
||||
|
||||
// 编码为 PEM
|
||||
String pemEncoded = PemEncoding.encode(keyPair.getPrivate());
|
||||
|
||||
// 从 PEM 解码
|
||||
PrivateKey decodedKey = PemEncoding.decode(pemEncoded);
|
||||
```
|
||||
|
||||
这个 API 减少了错误风险,简化了合规性要求,并通过简化企业、云和监管需求的加密设置和集成,增强了安全 Java 应用程序的可移植性和互操作性。
|
||||
|
||||
## JEP 529: Vector API(向量 API, 第十一次孵化)
|
||||
|
||||
向量计算由对向量的一系列操作组成。向量 API 用来表达向量计算,该计算可以在运行时可靠地编译为支持的 CPU 架构上的最佳向量指令,从而实现优于等效标量计算的性能。
|
||||
|
||||
向量 API 的目标是为用户提供简洁易用且与平台无关的表达范围广泛的向量计算。
|
||||
|
||||
这是对数组元素的简单标量计算:
|
||||
|
||||
```java
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这是使用 Vector API 进行的等效向量计算:
|
||||
|
||||
```java
|
||||
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
|
||||
|
||||
void vectorComputation(float[] a, float[] b, float[] c) {
|
||||
int i = 0;
|
||||
int upperBound = SPECIES.loopBound(a.length);
|
||||
for (; i < upperBound; i += SPECIES.length()) {
|
||||
// FloatVector va, vb, vc;
|
||||
var va = FloatVector.fromArray(SPECIES, a, i);
|
||||
var vb = FloatVector.fromArray(SPECIES, b, i);
|
||||
var vc = va.mul(va)
|
||||
.add(vb.mul(vb))
|
||||
.neg();
|
||||
vc.intoArray(c, i);
|
||||
}
|
||||
for (; i < a.length; i++) {
|
||||
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
尽管仍在孵化中,但其第十一次迭代足以证明其重要性。它使得 Java 在科学计算、机器学习、AI 推理、大数据处理等性能敏感领域,能够编写出接近甚至媲美 C++ 等本地语言性能的代码。
|
||||
|
||||
## JEP 504: 移除 Applet API
|
||||
|
||||
Applet API 在 JDK 9 中被标记为废弃,在 JDK 17 中被标记为即将移除。在 JDK 26 中,Applet API 终于被**完全移除**。大快人心啊!
|
||||
|
||||
这意味着:
|
||||
|
||||
- `java.applet.Applet` 类及其相关类已被删除。
|
||||
- 减少了 JDK 的安装和源代码体积。
|
||||
- 提升了应用程序的性能、稳定性和安全性。
|
||||
|
||||
Applet 技术早已过时,现代 Web 开发已完全转向其他技术栈。移除这个遗留 API 是 Java 平台现代化的必要步骤。
|
||||
|
||||
## 总结
|
||||
|
||||
JDK 26 虽然是一个非 LTS 版本,但包含了一些值得关注的重要特性:
|
||||
|
||||
| 类别 | 特性 |
|
||||
| -------- | ---------------------------------------------------------- |
|
||||
| **网络** | HTTP/3 支持 |
|
||||
| **性能** | G1 GC 吞吐量优化、AOT 缓存支持任意 GC |
|
||||
| **语言** | 模式匹配支持基本类型(第四次预览)、延迟常量(第二次预览) |
|
||||
| **并发** | 结构化并发(第六次预览)、向量 API(第十一次孵化) |
|
||||
| **安全** | 让 final 真正不可变、PEM 编码支持 |
|
||||
| **清理** | 移除 Applet API |
|
||||
|
||||
Oracle 将提供更新直到 2026 年 9 月,届时将被 Oracle JDK 27 取代。
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,928 @@
|
||||
---
|
||||
title: 《Java8 指南》中文翻译
|
||||
description: 翻译与整理 Java 8 教程,涵盖 Lambda、方法引用、接口默认方法、Stream 等新特性与示例代码。
|
||||
category: Java
|
||||
tag:
|
||||
- Java新特性
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java 8,指南,Lambda,方法引用,默认方法,Stream API,函数式接口,Date/Time API
|
||||
---
|
||||
|
||||
#《Java8 指南》中文翻译
|
||||
|
||||
JDK 8 于 2014 年 3 月 18 日发布,这是一个 LTS(长期支持版)版本,是 Java 历史上最重要的版本之一。至此为止,目前有 JDK8、JDK11、JDK17、JDK21 和 JDK 25 这五个长期支持版了。
|
||||
|
||||
JDK 8 引入了许多重要的新特性,这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||||
|
||||
- Lambda 表达式
|
||||
- 方法引用
|
||||
- 接口默认方法
|
||||
- Stream API
|
||||
- 函数式接口
|
||||
- Optional 类
|
||||
- Date/Time API
|
||||
- 注解增强
|
||||
|
||||
下图是从 JDK 8 到 JDK 24 每个版本的更新带来的新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
随着 Java 8 的普及度越来越高,很多人都提到面试中关于 Java 8 也是非常常问的知识点。应各位要求和需要,我打算对这部分知识做一个总结。本来准备自己总结的,后面看到 GitHub 上有一个相关的仓库,地址:
|
||||
[https://github.com/winterbe/java8-tutorial](https://github.com/winterbe/java8-tutorial)。这个仓库是英文的,我对其进行了翻译并添加和修改了部分内容,下面是正文。
|
||||
|
||||
---
|
||||
|
||||
欢迎阅读我对 Java 8 的介绍。本教程将逐步指导您完成所有新语言功能。 在简短的代码示例的基础上,您将学习如何使用默认接口方法,lambda 表达式,方法引用和可重复注释。 在本文的最后,您将熟悉最新的 API 更改,如流,函数式接口(Functional Interfaces),Map 类的扩展和新的 Date API。 没有大段枯燥的文字,只有一堆注释的代码片段。
|
||||
|
||||
## 接口的默认方法(Default Methods for Interfaces)
|
||||
|
||||
Java 8 使我们能够通过使用 `default` 关键字向接口添加非抽象方法实现。 此功能也称为[虚拟扩展方法](http://stackoverflow.com/a/24102730)。
|
||||
|
||||
第一个例子:
|
||||
|
||||
```java
|
||||
interface Formula{
|
||||
|
||||
double calculate(int a);
|
||||
|
||||
default double sqrt(int a) {
|
||||
return Math.sqrt(a);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Formula 接口中除了抽象方法计算接口公式还定义了默认方法 `sqrt`。 实现该接口的类只需要实现抽象方法 `calculate`。 默认方法 `sqrt` 可以直接使用。当然你也可以直接通过接口创建对象,然后实现接口中的默认方法就可以了,我们通过代码演示一下这种方式。
|
||||
|
||||
```java
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// 通过匿名内部类方式访问接口
|
||||
Formula formula = new Formula() {
|
||||
@Override
|
||||
public double calculate(int a) {
|
||||
return sqrt(a * 100);
|
||||
}
|
||||
};
|
||||
|
||||
System.out.println(formula.calculate(100)); // 100.0
|
||||
System.out.println(formula.sqrt(16)); // 4.0
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
formula 是作为匿名对象实现的。该代码非常容易理解,6 行代码实现了计算 `sqrt(a * 100)`。在下一节中,我们将会看到在 Java 8 中实现单个方法对象有一种更好更方便的方法。
|
||||
|
||||
**译者注:** 不管是抽象类还是接口,都可以通过匿名内部类的方式访问。不能通过抽象类或者接口直接创建对象。对于上面通过匿名内部类方式访问接口,我们可以这样理解:一个内部类实现了接口里的抽象方法并且返回一个内部类对象,之后我们让接口的引用来指向这个对象。
|
||||
|
||||
## Lambda 表达式(Lambda expressions)
|
||||
|
||||
首先看看在老版本的 Java 中是如何排列字符串的:
|
||||
|
||||
```java
|
||||
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
|
||||
|
||||
Collections.sort(names, new Comparator<String>() {
|
||||
@Override
|
||||
public int compare(String a, String b) {
|
||||
return b.compareTo(a);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
只需要给静态方法 `Collections.sort` 传入一个 List 对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给 `sort` 方法。
|
||||
|
||||
在 Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8 提供了更简洁的语法,lambda 表达式:
|
||||
|
||||
```java
|
||||
Collections.sort(names, (String a, String b) -> {
|
||||
return b.compareTo(a);
|
||||
});
|
||||
```
|
||||
|
||||
可以看出,代码变得更短且更具有可读性,但是实际上还可以写得更短:
|
||||
|
||||
```java
|
||||
Collections.sort(names, (String a, String b) -> b.compareTo(a));
|
||||
```
|
||||
|
||||
对于函数体只有一行代码的,你可以去掉大括号{}以及 return 关键字,但是你还可以写得更短点:
|
||||
|
||||
```java
|
||||
names.sort((a, b) -> b.compareTo(a));
|
||||
```
|
||||
|
||||
List 类本身就有一个 `sort` 方法。并且 Java 编译器可以自动推导出参数类型,所以你可以不用再写一次类型。接下来我们看看 lambda 表达式还有什么其他用法。
|
||||
|
||||
## 函数式接口(Functional Interfaces)
|
||||
|
||||
**译者注:** 原文对这部分解释不太清楚,故做了修改!
|
||||
|
||||
Java 语言设计者们投入了大量精力来思考如何使现有的函数友好地支持 Lambda。最终采取的方法是:增加函数式接口的概念。**“函数式接口”是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是上面提到的默认方法)的接口。** 像这样的接口,可以被隐式转换为 lambda 表达式。`java.lang.Runnable` 与 `java.util.concurrent.Callable` 是函数式接口最典型的两个例子。Java 8 增加了一种特殊的注解 `@FunctionalInterface`,但是这个注解通常不是必须的(某些情况建议使用),只要接口只包含一个抽象方法,虚拟机会自动判断该接口为函数式接口。一般建议在接口上使用 `@FunctionalInterface` 注解进行声明,这样的话,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的,如下图所示
|
||||
|
||||

|
||||
|
||||
示例:
|
||||
|
||||
```java
|
||||
@FunctionalInterface
|
||||
public interface Converter<F, T> {
|
||||
T convert(F from);
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// TODO 将数字字符串转换为整数类型
|
||||
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
|
||||
Integer converted = converter.convert("123");
|
||||
System.out.println(converted.getClass()); //class java.lang.Integer
|
||||
```
|
||||
|
||||
**译者注:** 大部分函数式接口都不用我们自己写,Java8 都给我们实现好了,这些接口都在 java.util.function 包里。
|
||||
|
||||
## 方法和构造函数引用(Method and Constructor References)
|
||||
|
||||
前一节中的代码还可以通过静态方法引用来表示:
|
||||
|
||||
```java
|
||||
Converter<String, Integer> converter = Integer::valueOf;
|
||||
Integer converted = converter.convert("123");
|
||||
System.out.println(converted.getClass()); //class java.lang.Integer
|
||||
```
|
||||
|
||||
Java 8 允许您通过 `::` 关键字传递方法或构造函数的引用。 上面的示例显示了如何引用静态方法。 但我们也可以引用对象方法:
|
||||
|
||||
```java
|
||||
class Something {
|
||||
String startsWith(String s) {
|
||||
return String.valueOf(s.charAt(0));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
Something something = new Something();
|
||||
Converter<String, String> converter = something::startsWith;
|
||||
String converted = converter.convert("Java");
|
||||
System.out.println(converted); // "J"
|
||||
```
|
||||
|
||||
接下来看看构造函数是如何使用 `::` 关键字来引用的,首先我们定义一个包含多个构造函数的简单类:
|
||||
|
||||
```java
|
||||
class Person {
|
||||
String firstName;
|
||||
String lastName;
|
||||
|
||||
Person() {}
|
||||
|
||||
Person(String firstName, String lastName) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
接下来我们指定一个用来创建 Person 对象的对象工厂接口:
|
||||
|
||||
```java
|
||||
interface PersonFactory<P extends Person> {
|
||||
P create(String firstName, String lastName);
|
||||
}
|
||||
```
|
||||
|
||||
这里我们使用构造函数引用来将他们关联起来,而不是手动实现一个完整的工厂:
|
||||
|
||||
```java
|
||||
PersonFactory<Person> personFactory = Person::new;
|
||||
Person person = personFactory.create("Peter", "Parker");
|
||||
```
|
||||
|
||||
我们只需要使用 `Person::new` 来获取 Person 类构造函数的引用,Java 编译器会自动根据 `PersonFactory.create` 方法的参数类型来选择合适的构造函数。
|
||||
|
||||
## Lambda 表达式作用域(Lambda Scopes)
|
||||
|
||||
### 访问局部变量
|
||||
|
||||
我们可以直接在 lambda 表达式中访问外部的局部变量:
|
||||
|
||||
```java
|
||||
final int num = 1;
|
||||
Converter<Integer, String> stringConverter =
|
||||
(from) -> String.valueOf(from + num);
|
||||
|
||||
stringConverter.convert(2); // 3
|
||||
```
|
||||
|
||||
但是和匿名对象不同的是,这里的变量 num 可以不用声明为 final,该代码同样正确:
|
||||
|
||||
```java
|
||||
int num = 1;
|
||||
Converter<Integer, String> stringConverter =
|
||||
(from) -> String.valueOf(from + num);
|
||||
|
||||
stringConverter.convert(2); // 3
|
||||
```
|
||||
|
||||
不过这里的 num 必须不可被后面的代码修改(即隐性的具有 final 的语义),例如下面的就无法编译:
|
||||
|
||||
```java
|
||||
int num = 1;
|
||||
Converter<Integer, String> stringConverter =
|
||||
(from) -> String.valueOf(from + num);
|
||||
num = 3;//在lambda表达式中试图修改num同样是不允许的。
|
||||
```
|
||||
|
||||
### 访问字段和静态变量
|
||||
|
||||
与局部变量相比,我们在 lambda 表达式中对实例字段和静态变量都有读写访问权限。 该行为和匿名对象是一致的。
|
||||
|
||||
```java
|
||||
class Lambda4 {
|
||||
static int outerStaticNum;
|
||||
int outerNum;
|
||||
|
||||
void testScopes() {
|
||||
Converter<Integer, String> stringConverter1 = (from) -> {
|
||||
outerNum = 23;
|
||||
return String.valueOf(from);
|
||||
};
|
||||
|
||||
Converter<Integer, String> stringConverter2 = (from) -> {
|
||||
outerStaticNum = 72;
|
||||
return String.valueOf(from);
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 访问默认接口方法
|
||||
|
||||
还记得第一节中的 formula 示例吗? `Formula` 接口定义了一个默认方法 `sqrt`,可以从包含匿名对象的每个 formula 实例访问该方法。 这不适用于 lambda 表达式。
|
||||
|
||||
无法从 lambda 表达式中访问默认方法,故以下代码无法编译:
|
||||
|
||||
```java
|
||||
Formula formula = (a) -> sqrt(a * 100);
|
||||
```
|
||||
|
||||
## 内置函数式接口(Built-in Functional Interfaces)
|
||||
|
||||
JDK 1.8 API 包含许多内置函数式接口。 其中一些接口在老版本的 Java 中是比较常见的比如:`Comparator` 或 `Runnable`,这些接口都增加了 `@FunctionalInterface` 注解以便能用在 lambda 表达式上。
|
||||
|
||||
但是 Java 8 API 同样还提供了很多全新的函数式接口来让你的编程工作更加方便,有一些接口是来自 [Google Guava](https://code.google.com/p/guava-libraries/) 库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到 lambda 上使用的。
|
||||
|
||||
### Predicate
|
||||
|
||||
Predicate 接口是只有一个参数的返回布尔类型值的 **断言型** 接口。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非):
|
||||
|
||||
**译者注:** Predicate 接口源码如下
|
||||
|
||||
```java
|
||||
package java.util.function;
|
||||
import java.util.Objects;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Predicate<T> {
|
||||
|
||||
// 该方法是接受一个传入类型,返回一个布尔值.此方法应用于判断.
|
||||
boolean test(T t);
|
||||
|
||||
//and方法与关系型运算符"&&"相似,两边都成立才返回true
|
||||
default Predicate<T> and(Predicate<? super T> other) {
|
||||
Objects.requireNonNull(other);
|
||||
return (t) -> test(t) && other.test(t);
|
||||
}
|
||||
// 与关系运算符"!"相似,对判断进行取反
|
||||
default Predicate<T> negate() {
|
||||
return (t) -> !test(t);
|
||||
}
|
||||
//or方法与关系型运算符"||"相似,两边只要有一个成立就返回true
|
||||
default Predicate<T> or(Predicate<? super T> other) {
|
||||
Objects.requireNonNull(other);
|
||||
return (t) -> test(t) || other.test(t);
|
||||
}
|
||||
// 该方法接收一个Object对象,返回一个Predicate类型.此方法用于判断第一个test的方法与第二个test方法相同(equal).
|
||||
static <T> Predicate<T> isEqual(Object targetRef) {
|
||||
return (null == targetRef)
|
||||
? Objects::isNull
|
||||
: object -> targetRef.equals(object);
|
||||
}
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```java
|
||||
Predicate<String> predicate = (s) -> s.length() > 0;
|
||||
|
||||
predicate.test("foo"); // true
|
||||
predicate.negate().test("foo"); // false
|
||||
|
||||
Predicate<Boolean> nonNull = Objects::nonNull;
|
||||
Predicate<Boolean> isNull = Objects::isNull;
|
||||
|
||||
Predicate<String> isEmpty = String::isEmpty;
|
||||
Predicate<String> isNotEmpty = isEmpty.negate();
|
||||
```
|
||||
|
||||
### Function
|
||||
|
||||
Function 接口接受一个参数并生成结果。默认方法可用于将多个函数链接在一起(compose, andThen):
|
||||
|
||||
**译者注:** Function 接口源码如下
|
||||
|
||||
```java
|
||||
|
||||
package java.util.function;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Function<T, R> {
|
||||
|
||||
//将Function对象应用到输入的参数上,然后返回计算结果。
|
||||
R apply(T t);
|
||||
//将两个Function整合,并返回一个能够执行两个Function对象功能的Function对象。
|
||||
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
|
||||
Objects.requireNonNull(before);
|
||||
return (V v) -> apply(before.apply(v));
|
||||
}
|
||||
//
|
||||
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
|
||||
Objects.requireNonNull(after);
|
||||
return (T t) -> after.apply(apply(t));
|
||||
}
|
||||
|
||||
static <T> Function<T, T> identity() {
|
||||
return t -> t;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
Function<String, Integer> toInteger = Integer::valueOf;
|
||||
Function<String, String> backToString = toInteger.andThen(String::valueOf);
|
||||
backToString.apply("123"); // "123"
|
||||
```
|
||||
|
||||
### Supplier
|
||||
|
||||
Supplier 接口产生给定泛型类型的结果。 与 Function 接口不同,Supplier 接口不接受参数。
|
||||
|
||||
```java
|
||||
Supplier<Person> personSupplier = Person::new;
|
||||
personSupplier.get(); // new Person
|
||||
```
|
||||
|
||||
### Consumer
|
||||
|
||||
Consumer 接口表示要对单个输入参数执行的操作。
|
||||
|
||||
```java
|
||||
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
|
||||
greeter.accept(new Person("Luke", "Skywalker"));
|
||||
```
|
||||
|
||||
### Comparator
|
||||
|
||||
Comparator 是老 Java 中的经典接口, Java 8 在此之上添加了多种默认方法:
|
||||
|
||||
```java
|
||||
Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
|
||||
|
||||
Person p1 = new Person("John", "Doe");
|
||||
Person p2 = new Person("Alice", "Wonderland");
|
||||
|
||||
comparator.compare(p1, p2); // > 0
|
||||
comparator.reversed().compare(p1, p2); // < 0
|
||||
```
|
||||
|
||||
## Optional
|
||||
|
||||
Optional 不是函数式接口,而是用于防止 NullPointerException 的漂亮工具。这是下一节的一个重要概念,让我们快速了解一下 Optional 的工作原理。
|
||||
|
||||
Optional 是一个简单的容器,其值可能是 null 或者不是 null。在 Java 8 之前一般某个函数应该返回非空对象但是有时却什么也没有返回,而在 Java 8 中,你应该返回 Optional 而不是 null。
|
||||
|
||||
译者注:示例中每个方法的作用已经添加。
|
||||
|
||||
```java
|
||||
//of():为非null的值创建一个Optional
|
||||
Optional<String> optional = Optional.of("bam");
|
||||
// isPresent():如果值存在返回true,否则返回false
|
||||
optional.isPresent(); // true
|
||||
//get():如果Optional有值则将其返回,否则抛出NoSuchElementException
|
||||
optional.get(); // "bam"
|
||||
//orElse():如果有值则将其返回,否则返回指定的其它值
|
||||
optional.orElse("fallback"); // "bam"
|
||||
//ifPresent():如果Optional实例有值则为其调用consumer,否则不做处理
|
||||
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
|
||||
```
|
||||
|
||||
推荐阅读:[[Java8]如何正确使用 Optional](https://blog.kaaass.net/archives/764)
|
||||
|
||||
## Streams(流)
|
||||
|
||||
`java.util.Stream` 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回 Stream 本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 `java.util.Collection` 的子类,List 或者 Set, Map 不支持。Stream 的操作可以串行执行或者并行执行。
|
||||
|
||||
首先看看 Stream 是怎么用,首先创建实例代码需要用到的数据 List:
|
||||
|
||||
```java
|
||||
List<String> stringList = new ArrayList<>();
|
||||
stringList.add("ddd2");
|
||||
stringList.add("aaa2");
|
||||
stringList.add("bbb1");
|
||||
stringList.add("aaa1");
|
||||
stringList.add("bbb3");
|
||||
stringList.add("ccc");
|
||||
stringList.add("bbb2");
|
||||
stringList.add("ddd1");
|
||||
```
|
||||
|
||||
Java 8 扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个 Stream。下面几节将详细解释常用的 Stream 操作:
|
||||
|
||||
### Filter(过滤)
|
||||
|
||||
过滤通过一个 predicate 接口来过滤并只保留符合条件的元素,该操作属于**中间操作**,所以我们可以在过滤后的结果来应用其他 Stream 操作(比如 forEach)。forEach 需要一个函数来对过滤后的元素依次执行。forEach 是一个最终操作,所以我们不能在 forEach 之后来执行其他 Stream 操作。
|
||||
|
||||
```java
|
||||
// 测试 Filter(过滤)
|
||||
stringList
|
||||
.stream()
|
||||
.filter((s) -> s.startsWith("a"))
|
||||
.forEach(System.out::println);//aaa2 aaa1
|
||||
```
|
||||
|
||||
forEach 是为 Lambda 而设计的,保持了最紧凑的风格。而且 Lambda 表达式本身是可以重用的,非常方便。
|
||||
|
||||
### Sorted(排序)
|
||||
|
||||
排序是一个 **中间操作**,返回的是排序好后的 Stream。**如果你不指定一个自定义的 Comparator 则会使用默认排序。**
|
||||
|
||||
```java
|
||||
// 测试 Sort (排序)
|
||||
stringList
|
||||
.stream()
|
||||
.sorted()
|
||||
.filter((s) -> s.startsWith("a"))
|
||||
.forEach(System.out::println);// aaa1 aaa2
|
||||
```
|
||||
|
||||
需要注意的是,排序只创建了一个排列好后的 Stream,而不会影响原有的数据源,排序之后原数据 stringList 是不会被修改的:
|
||||
|
||||
```java
|
||||
System.out.println(stringList);// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
|
||||
```
|
||||
|
||||
### Map(映射)
|
||||
|
||||
中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象。
|
||||
|
||||
下面的示例展示了将字符串转换为大写字符串。你也可以通过 map 来将对象转换成其他类型,map 返回的 Stream 类型是根据你 map 传递进去的函数的返回值决定的。
|
||||
|
||||
```java
|
||||
// 测试 Map 操作
|
||||
stringList
|
||||
.stream()
|
||||
.map(String::toUpperCase)
|
||||
.sorted((a, b) -> b.compareTo(a))
|
||||
.forEach(System.out::println);// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "BBB1", "AAA2", "AAA1"
|
||||
```
|
||||
|
||||
### Match(匹配)
|
||||
|
||||
Stream 提供了多种匹配操作,允许检测指定的 Predicate 是否匹配整个 Stream。所有的匹配操作都是 **最终操作**,并返回一个 boolean 类型的值。
|
||||
|
||||
```java
|
||||
// 测试 Match (匹配)操作
|
||||
boolean anyStartsWithA =
|
||||
stringList
|
||||
.stream()
|
||||
.anyMatch((s) -> s.startsWith("a"));
|
||||
System.out.println(anyStartsWithA); // true
|
||||
|
||||
boolean allStartsWithA =
|
||||
stringList
|
||||
.stream()
|
||||
.allMatch((s) -> s.startsWith("a"));
|
||||
|
||||
System.out.println(allStartsWithA); // false
|
||||
|
||||
boolean noneStartsWithZ =
|
||||
stringList
|
||||
.stream()
|
||||
.noneMatch((s) -> s.startsWith("z"));
|
||||
|
||||
System.out.println(noneStartsWithZ); // true
|
||||
```
|
||||
|
||||
### Count(计数)
|
||||
|
||||
计数是一个 **最终操作**,返回 Stream 中元素的个数,**返回值类型是 long**。
|
||||
|
||||
```java
|
||||
//测试 Count (计数)操作
|
||||
long startsWithB =
|
||||
stringList
|
||||
.stream()
|
||||
.filter((s) -> s.startsWith("b"))
|
||||
.count();
|
||||
System.out.println(startsWithB); // 3
|
||||
```
|
||||
|
||||
### Reduce(规约)
|
||||
|
||||
这是一个 **最终操作**,允许通过指定的函数来将 stream 中的多个元素规约为一个元素,规约后的结果是通过 Optional 接口表示的:
|
||||
|
||||
```java
|
||||
//测试 Reduce (规约)操作
|
||||
Optional<String> reduced =
|
||||
stringList
|
||||
.stream()
|
||||
.sorted()
|
||||
.reduce((s1, s2) -> s1 + "#" + s2);
|
||||
|
||||
reduced.ifPresent(System.out::println);//aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2
|
||||
```
|
||||
|
||||
**译者注:** 这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于 `Integer sum = integers.reduce(0, (a, b) -> a+b);` 也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。
|
||||
|
||||
```java
|
||||
// 字符串连接,concat = "ABCD"
|
||||
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
|
||||
// 求最小值,minValue = -3.0
|
||||
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
|
||||
// 求和,sumValue = 10, 有起始值
|
||||
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
|
||||
// 求和,sumValue = 10, 无起始值
|
||||
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
|
||||
// 过滤,字符串连接,concat = "ace"
|
||||
concat = Stream.of("a", "B", "c", "D", "e", "F").
|
||||
filter(x -> x.compareTo("Z") > 0).
|
||||
reduce("", String::concat);
|
||||
```
|
||||
|
||||
上面代码例如第一个示例的 reduce(),第一个参数(空白字符)即为起始值,第二个参数(String::concat)为 BinaryOperator。这类有起始值的 reduce() 都返回具体的对象。而对于第四个示例没有起始值的 reduce(),由于可能没有足够的元素,返回的是 Optional,请留意这个区别。更多内容查看:[IBM:Java 8 中的 Streams API 详解](https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/index.html)
|
||||
|
||||
## Parallel Streams(并行流)
|
||||
|
||||
前面提到过 Stream 有串行和并行两种,串行 Stream 上的操作是在一个线程中依次完成,而并行 Stream 则是在多个线程上同时执行。
|
||||
|
||||
下面的例子展示了是如何通过并行 Stream 来提升性能:
|
||||
|
||||
首先我们创建一个没有重复元素的大表:
|
||||
|
||||
```java
|
||||
int max = 1000000;
|
||||
List<String> values = new ArrayList<>(max);
|
||||
for (int i = 0; i < max; i++) {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
values.add(uuid.toString());
|
||||
}
|
||||
```
|
||||
|
||||
我们分别用串行和并行两种方式对其进行排序,最后看看所用时间的对比。
|
||||
|
||||
### Sequential Sort(串行排序)
|
||||
|
||||
```java
|
||||
//串行排序
|
||||
long t0 = System.nanoTime();
|
||||
long count = Arrays.stream(list.stream().sorted().toArray()).count();
|
||||
System.out.println(count);
|
||||
|
||||
long t1 = System.nanoTime();
|
||||
|
||||
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
|
||||
System.out.println(String.format("sequential sort took: %d ms", millis));
|
||||
```
|
||||
|
||||
```plain
|
||||
1000000
|
||||
sequential sort took: 709 ms//串行排序所用的时间
|
||||
```
|
||||
|
||||
### Parallel Sort(并行排序)
|
||||
|
||||
```java
|
||||
//并行排序
|
||||
long t0 = System.nanoTime();
|
||||
|
||||
long count = Arrays.stream(list.parallelStream().sorted().toArray()).count();
|
||||
System.out.println(count);
|
||||
|
||||
long t1 = System.nanoTime();
|
||||
|
||||
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
|
||||
System.out.println(String.format("parallel sort took: %d ms", millis));
|
||||
|
||||
```
|
||||
|
||||
```java
|
||||
1000000
|
||||
parallel sort took: 475 ms//并行排序所用的时间
|
||||
```
|
||||
|
||||
上面两个代码几乎是一样的,但是并行版的快了 50% 左右,唯一需要做的改动就是将 `stream()` 改为 `parallelStream()`。
|
||||
|
||||
## Maps
|
||||
|
||||
前面提到过,Map 类型不支持 streams,不过 Map 提供了一些新的有用的方法来处理一些日常任务。Map 接口本身没有可用的 `stream()` 方法,但是你可以在键,值上创建专门的流或者通过 `map.keySet().stream()`,`map.values().stream()` 和 `map.entrySet().stream()`。
|
||||
|
||||
此外,Maps 支持各种新的和有用的方法来执行常见任务。
|
||||
|
||||
```java
|
||||
Map<Integer, String> map = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
map.putIfAbsent(i, "val" + i);
|
||||
}
|
||||
|
||||
map.forEach((id, val) -> System.out.println(val));//val0 val1 val2 val3 val4 val5 val6 val7 val8 val9
|
||||
```
|
||||
|
||||
`putIfAbsent` 阻止我们在 null 检查时写入额外的代码;`forEach` 接受一个 consumer 来对 map 中的每个元素操作。
|
||||
|
||||
此示例显示如何使用函数在 map 上计算代码:
|
||||
|
||||
```java
|
||||
map.computeIfPresent(3, (num, val) -> val + num);
|
||||
map.get(3); // val33
|
||||
|
||||
map.computeIfPresent(9, (num, val) -> null);
|
||||
map.containsKey(9); // false
|
||||
|
||||
map.computeIfAbsent(23, num -> "val" + num);
|
||||
map.containsKey(23); // true
|
||||
|
||||
map.computeIfAbsent(3, num -> "bam");
|
||||
map.get(3); // val33
|
||||
```
|
||||
|
||||
接下来展示如何在 Map 里删除一个键值全都匹配的项:
|
||||
|
||||
```java
|
||||
map.remove(3, "val3");
|
||||
map.get(3); // val33
|
||||
map.remove(3, "val33");
|
||||
map.get(3); // null
|
||||
```
|
||||
|
||||
另外一个有用的方法:
|
||||
|
||||
```java
|
||||
map.getOrDefault(42, "not found"); // not found
|
||||
```
|
||||
|
||||
对 Map 的元素做合并也变得很容易了:
|
||||
|
||||
```java
|
||||
map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
|
||||
map.get(9); // val9
|
||||
map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
|
||||
map.get(9); // val9concat
|
||||
```
|
||||
|
||||
Merge 做的事情是如果键名不存在则插入,否则对原键对应的值做合并操作并重新插入到 map 中。
|
||||
|
||||
## Date API(日期相关 API)
|
||||
|
||||
Java 8 在 `java.time` 包下包含一个全新的日期和时间 API。新的 Date API 与 Joda-Time 库相似,但它们不一样。以下示例涵盖了此新 API 的最重要部分。译者对这部分内容参考相关书籍做了大部分修改。
|
||||
|
||||
**译者注(总结):**
|
||||
|
||||
- Clock 类提供了访问当前日期和时间的方法,Clock 是时区敏感的,可以用来取代 `System.currentTimeMillis()` 来获取当前的微秒数。某一个特定的时间点也可以使用 `Instant` 类来表示,`Instant` 类也可以用来创建旧版本的 `java.util.Date` 对象。
|
||||
|
||||
- 在新 API 中时区使用 ZoneId 来表示。时区可以很方便的使用静态方法 of 来获取到。 抽象类 `ZoneId`(在 `java.time` 包中)表示一个区域标识符。 它有一个名为 `getAvailableZoneIds` 的静态方法,它返回所有区域标识符。
|
||||
|
||||
- jdk1.8 中新增了 LocalDate 与 LocalDateTime 等类来解决日期处理方法,同时引入了一个新的类 DateTimeFormatter 来解决日期格式化问题。可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat。
|
||||
|
||||
### Clock
|
||||
|
||||
Clock 类提供了访问当前日期和时间的方法,Clock 是时区敏感的,可以用来取代 `System.currentTimeMillis()` 来获取当前的微秒数。某一个特定的时间点也可以使用 `Instant` 类来表示,`Instant` 类也可以用来创建旧版本的 `java.util.Date` 对象。
|
||||
|
||||
```java
|
||||
Clock clock = Clock.systemDefaultZone();
|
||||
long millis = clock.millis();
|
||||
System.out.println(millis);//1552379579043
|
||||
Instant instant = clock.instant();
|
||||
System.out.println(instant);
|
||||
Date legacyDate = Date.from(instant); //2019-03-12T08:46:42.588Z
|
||||
System.out.println(legacyDate);//Tue Mar 12 16:32:59 CST 2019
|
||||
```
|
||||
|
||||
### Timezones(时区)
|
||||
|
||||
在新 API 中时区使用 ZoneId 来表示。时区可以很方便的使用静态方法 of 来获取到。 抽象类 `ZoneId`(在 `java.time` 包中)表示一个区域标识符。 它有一个名为 `getAvailableZoneIds` 的静态方法,它返回所有区域标识符。
|
||||
|
||||
```java
|
||||
//输出所有区域标识符
|
||||
System.out.println(ZoneId.getAvailableZoneIds());
|
||||
|
||||
ZoneId zone1 = ZoneId.of("Europe/Berlin");
|
||||
ZoneId zone2 = ZoneId.of("Brazil/East");
|
||||
System.out.println(zone1.getRules());// ZoneRules[currentStandardOffset=+01:00]
|
||||
System.out.println(zone2.getRules());// ZoneRules[currentStandardOffset=-03:00]
|
||||
```
|
||||
|
||||
### LocalTime(本地时间)
|
||||
|
||||
LocalTime 定义了一个没有时区信息的时间,例如 晚上 10 点或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:
|
||||
|
||||
```java
|
||||
LocalTime now1 = LocalTime.now(zone1);
|
||||
LocalTime now2 = LocalTime.now(zone2);
|
||||
System.out.println(now1.isBefore(now2)); // false
|
||||
|
||||
long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
|
||||
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
|
||||
|
||||
System.out.println(hoursBetween); // -3
|
||||
System.out.println(minutesBetween); // -239
|
||||
```
|
||||
|
||||
LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串.
|
||||
|
||||
```java
|
||||
LocalTime late = LocalTime.of(23, 59, 59);
|
||||
System.out.println(late); // 23:59:59
|
||||
DateTimeFormatter germanFormatter =
|
||||
DateTimeFormatter
|
||||
.ofLocalizedTime(FormatStyle.SHORT)
|
||||
.withLocale(Locale.GERMAN);
|
||||
|
||||
LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
|
||||
System.out.println(leetTime); // 13:37
|
||||
```
|
||||
|
||||
### LocalDate(本地日期)
|
||||
|
||||
LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和 LocalTime 基本一致。下面的例子展示了如何给 Date 对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。
|
||||
|
||||
```java
|
||||
LocalDate today = LocalDate.now();//获取现在的日期
|
||||
System.out.println("今天的日期: "+today);//2019-03-12
|
||||
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
|
||||
System.out.println("明天的日期: "+tomorrow);//2019-03-13
|
||||
LocalDate yesterday = tomorrow.minusDays(2);
|
||||
System.out.println("昨天的日期: "+yesterday);//2019-03-11
|
||||
LocalDate independenceDay = LocalDate.of(2019, Month.MARCH, 12);
|
||||
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
|
||||
System.out.println("今天是周几:"+dayOfWeek);//TUESDAY
|
||||
```
|
||||
|
||||
从字符串解析一个 LocalDate 类型和解析 LocalTime 一样简单,下面是使用 `DateTimeFormatter` 解析字符串的例子:
|
||||
|
||||
```java
|
||||
String str1 = "2014==04==12 01时06分09秒";
|
||||
// 根据需要解析的日期、时间字符串定义解析所用的格式器
|
||||
DateTimeFormatter fomatter1 = DateTimeFormatter
|
||||
.ofPattern("yyyy==MM==dd HH时mm分ss秒");
|
||||
|
||||
LocalDateTime dt1 = LocalDateTime.parse(str1, fomatter1);
|
||||
System.out.println(dt1); // 输出 2014-04-12T01:06:09
|
||||
|
||||
String str2 = "2014$$$四月$$$13 20小时";
|
||||
DateTimeFormatter fomatter2 = DateTimeFormatter
|
||||
.ofPattern("yyy$$$MMM$$$dd HH小时");
|
||||
LocalDateTime dt2 = LocalDateTime.parse(str2, fomatter2);
|
||||
System.out.println(dt2); // 输出 2014-04-13T20:00
|
||||
|
||||
```
|
||||
|
||||
再来看一个使用 `DateTimeFormatter` 格式化日期的示例
|
||||
|
||||
```java
|
||||
LocalDateTime rightNow=LocalDateTime.now();
|
||||
String date=DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(rightNow);
|
||||
System.out.println(date);//2019-03-12T16:26:48.29
|
||||
DateTimeFormatter formatter=DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss");
|
||||
System.out.println(formatter.format(rightNow));//2019-03-12 16:26:48
|
||||
```
|
||||
|
||||
**🐛 修正(参见:[issue#1157](https://github.com/Snailclimb/JavaGuide/issues/1157))**:使用 `YYYY` 显示年份时,会显示当前时间所在周的年份,在跨年周会有问题。一般情况下都使用 `yyyy`,来显示准确的年份。
|
||||
|
||||
跨年导致日期显示错误示例:
|
||||
|
||||
```java
|
||||
LocalDateTime rightNow = LocalDateTime.of(2020, 12, 31, 12, 0, 0);
|
||||
String date= DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(rightNow);
|
||||
// 2020-12-31T12:00:00
|
||||
System.out.println(date);
|
||||
DateTimeFormatter formatterOfYYYY = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss");
|
||||
// 2021-12-31 12:00:00
|
||||
System.out.println(formatterOfYYYY.format(rightNow));
|
||||
|
||||
DateTimeFormatter formatterOfYyyy = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
// 2020-12-31 12:00:00
|
||||
System.out.println(formatterOfYyyy.format(rightNow));
|
||||
```
|
||||
|
||||
从下图可以更清晰的看到具体的错误,并且 IDEA 已经智能地提示更倾向于使用 `yyyy` 而不是 `YYYY`。
|
||||
|
||||

|
||||
|
||||
### LocalDateTime(本地日期时间)
|
||||
|
||||
LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime 和 LocalTime 还有 LocalDate 一样,都是不可变的。LocalDateTime 提供了一些能访问具体字段的方法。
|
||||
|
||||
```java
|
||||
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
|
||||
|
||||
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
|
||||
System.out.println(dayOfWeek); // WEDNESDAY
|
||||
|
||||
Month month = sylvester.getMonth();
|
||||
System.out.println(month); // DECEMBER
|
||||
|
||||
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
|
||||
System.out.println(minuteOfDay); // 1439
|
||||
```
|
||||
|
||||
只要附加上时区信息,就可以将其转换为一个时间点 Instant 对象,Instant 时间点对象可以很容易的转换为老式的 `java.util.Date`。
|
||||
|
||||
```java
|
||||
Instant instant = sylvester
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toInstant();
|
||||
|
||||
Date legacyDate = Date.from(instant);
|
||||
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
|
||||
```
|
||||
|
||||
格式化 LocalDateTime 和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:
|
||||
|
||||
```java
|
||||
DateTimeFormatter formatter =
|
||||
DateTimeFormatter
|
||||
.ofPattern("MMM dd, yyyy - HH:mm");
|
||||
LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
|
||||
String string = formatter.format(parsed);
|
||||
System.out.println(string); // Nov 03, 2014 - 07:13
|
||||
```
|
||||
|
||||
和 java.text.NumberFormat 不一样的是新版的 DateTimeFormatter 是不可变的,所以它是线程安全的。
|
||||
关于时间日期格式的详细信息在[这里](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html)。
|
||||
|
||||
## Annotations(注解)
|
||||
|
||||
在 Java 8 中支持多重注解了,先看个例子来理解一下是什么意思。
|
||||
首先定义一个包装类 Hints 注解用来放置一组具体的 Hint 注解:
|
||||
|
||||
```java
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Hints {
|
||||
Hint[] value();
|
||||
}
|
||||
@Repeatable(Hints.class)
|
||||
@interface Hint {
|
||||
String value();
|
||||
}
|
||||
```
|
||||
|
||||
Java 8 允许我们把同一个类型的注解使用多次,只需要给该注解标注一下 `@Repeatable` 即可。
|
||||
|
||||
例 1: 使用包装类当容器来存多个注解(老方法)
|
||||
|
||||
```java
|
||||
@Hints({@Hint("hint1"), @Hint("hint2")})
|
||||
class Person {}
|
||||
```
|
||||
|
||||
例 2:使用多重注解(新方法)
|
||||
|
||||
```java
|
||||
@Hint("hint1")
|
||||
@Hint("hint2")
|
||||
class Person {}
|
||||
```
|
||||
|
||||
第二个例子里 java 编译器会隐性的帮你定义好@Hints 注解,了解这一点有助于你用反射来获取这些信息:
|
||||
|
||||
```java
|
||||
Hint hint = Person.class.getAnnotation(Hint.class);
|
||||
System.out.println(hint); // null
|
||||
Hints hints1 = Person.class.getAnnotation(Hints.class);
|
||||
System.out.println(hints1.value().length); // 2
|
||||
|
||||
Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class);
|
||||
System.out.println(hints2.length); // 2
|
||||
```
|
||||
|
||||
即便我们没有在 `Person` 类上定义 `@Hints` 注解,我们还是可以通过 `getAnnotation(Hints.class)` 来获取 `@Hints` 注解,更加方便的方法是使用 `getAnnotationsByType` 可以直接获取到所有的 `@Hint` 注解。
|
||||
另外 Java 8 的注解还增加到两种新的 target 上了:
|
||||
|
||||
```java
|
||||
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
|
||||
@interface MyAnnotation {}
|
||||
```
|
||||
|
||||
## Where to go from here?
|
||||
|
||||
关于 Java 8 的新特性就写到这了,肯定还有更多的特性等待发掘。JDK 1.8 里还有很多很有用的东西,比如 `Arrays.parallelSort`, `StampedLock` 和 `CompletableFuture` 等等。
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
@@ -0,0 +1,280 @@
|
||||
---
|
||||
title: Java 9 新特性概览
|
||||
description: 解析 Java 9 的模块化系统与 jlink 等更新,理解对运行时镜像与库使用的影响。
|
||||
category: Java
|
||||
tag:
|
||||
- Java新特性
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java 9,JDK9,模块化,JPMS,jlink,集合工厂方法,新 API
|
||||
---
|
||||
|
||||
**Java 9** 发布于 2017 年 9 月 21 日。作为 Java 8 之后 3 年半才发布的新版本,Java 9 带来了很多重大的变化其中最重要的改动是 Java 平台模块系统的引入,其他还有诸如集合、`Stream` 流……
|
||||
|
||||
JDK 9 不是 LTS(长期支持版),至此为止,目前有 JDK8、JDK11、JDK17、JDK21 这四个长期支持版了。
|
||||
|
||||
这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
|
||||
|
||||
- [JEP 222: Java Shell Tool (JShell)](https://openjdk.org/jeps/222)
|
||||
- [JEP 261: Module System (模块化系统)](https://openjdk.org/jeps/261)
|
||||
- [JEP 248: G1 Becomes the Default Garbage Collector (G1 成为默认垃圾回收器)](https://openjdk.org/jeps/248)
|
||||
- [JEP 254: Compact Strings (紧凑字符串)](https://openjdk.org/jeps/254)
|
||||
- [JEP 193: Variable Handles (变量句柄)](https://openjdk.org/jeps/193)
|
||||
|
||||
下图是从 JDK 8 到 JDK 25 每个版本的更新带来的新特性数量和更新时间:
|
||||
|
||||

|
||||
|
||||
## JEP 222: Java Shell Tool (JShell)
|
||||
|
||||
JShell 是 Java 9 新增的一个实用工具。为 Java 提供了类似于 Python 的实时命令行交互工具。
|
||||
|
||||
在 JShell 中可以直接输入表达式并查看其执行结果。
|
||||
|
||||

|
||||
|
||||
**JShell 为我们带来了哪些好处呢?**
|
||||
|
||||
1. 降低了输出第一行 Java 版"Hello World!"的门槛,能够提高新手的学习热情。
|
||||
2. 在处理简单的小逻辑,验证简单的小问题时,比 IDE 更有效率(并不是为了取代 IDE,对于复杂逻辑的验证,IDE 更合适,两者互补)。
|
||||
3. ……
|
||||
|
||||
**JShell 的代码和普通的可编译代码,有什么不一样?**
|
||||
|
||||
1. 一旦语句输入完成,JShell 立即就能返回执行的结果,而不再需要编辑器、编译器、解释器。
|
||||
2. JShell 支持变量的重复声明,后面声明的会覆盖前面声明的。
|
||||
3. JShell 支持独立的表达式比如普通的加法运算 `1 + 1`。
|
||||
4. ……
|
||||
|
||||
## JEP 261: Module System(模块化系统)
|
||||
|
||||
模块系统是[Jigsaw Project](https://openjdk.java.net/projects/jigsaw/)的一部分,把模块化开发实践引入到了 Java 平台中,可以让我们的代码可重用性更好!
|
||||
|
||||
**什么是模块系统?** 官方的定义是:
|
||||
|
||||
> A uniquely named, reusable group of related packages, as well as resources (such as images and XML files) and a module descriptor。
|
||||
|
||||
简单来说,你可以将一个模块看作是一组唯一命名、可重用的包、资源和模块描述文件(`module-info.java`)。
|
||||
|
||||
任意一个 jar 文件,只要加上一个模块描述文件(`module-info.java`),就可以升级为一个模块。
|
||||
|
||||

|
||||
|
||||
在引入了模块系统之后,JDK 被重新组织成 94 个模块。Java 应用可以通过新增的 **[jlink](http://openjdk.java.net/jeps/282) 工具**(Jlink 是随 Java 9 一起发布的新命令行工具。它允许开发人员为基于模块的 Java 应用程序创建自己的轻量级、定制的 JRE),创建出只包含所依赖的 JDK 模块的自定义运行时镜像。这样可以极大的减少 Java 运行时环境的大小。
|
||||
|
||||
我们可以通过 `exports` 关键字精准控制哪些类可以对外开放使用,哪些类只能内部使用。
|
||||
|
||||
```java
|
||||
module my.module {
|
||||
//exports 公开指定包的所有公共成员
|
||||
exports com.my.package.name;
|
||||
}
|
||||
|
||||
module my.module {
|
||||
//exports…to 限制访问的成员范围
|
||||
exports com.my.package.name to com.specific.package;
|
||||
}
|
||||
```
|
||||
|
||||
想要深入了解 Java 9 的模块化,可以参考下面这几篇文章:
|
||||
|
||||
- [《Project Jigsaw: Module System Quick-Start Guide》](https://openjdk.java.net/projects/jigsaw/quick-start)
|
||||
- [《Java 9 Modules: part 1》](https://stacktraceguru.com/java9/module-introduction)
|
||||
- [Java 9 揭秘(2. 模块化系统)](http://www.cnblogs.com/IcanFixIt/p/6947763.html)
|
||||
|
||||
## JEP 248: G1 Becomes the Default Garbage Collector(G1 成为默认垃圾回收器)
|
||||
|
||||
在 Java 8 的时候,默认垃圾回收器是 Parallel Scavenge(新生代)+Parallel Old(老年代)。到了 Java 9, CMS 垃圾回收器被废弃了,**G1(Garbage-First Garbage Collector)** 成为了默认垃圾回收器。
|
||||
|
||||
G1 还是在 Java 7 中被引入的,经过两个版本优异的表现成为默认垃圾回收器。
|
||||
|
||||
## JEP 193: Variable Handles(变量句柄)
|
||||
|
||||
变量句柄是一个变量或一组变量的引用,包括静态域,非静态域,数组元素和堆外数据结构中的组成部分等。
|
||||
|
||||
变量句柄的含义类似于已有的方法句柄 `MethodHandle`,由 Java 类 `java.lang.invoke.VarHandle` 来表示,可以使用类 `java.lang.invoke.MethodHandles.Lookup` 中的静态工厂方法来创建 `VarHandle` 对象。
|
||||
|
||||
`VarHandle` 的出现替代了 `java.util.concurrent.atomic` 和 `sun.misc.Unsafe` 的部分操作。并且提供了一系列标准的内存屏障操作,用于更加细粒度的控制内存排序。在安全性、可用性、性能上都要优于现有的 API。
|
||||
|
||||
## API 增强
|
||||
|
||||
并不是所有的 API 改动都会通过 JEP(Java Enhancement Proposal)来发布。
|
||||
|
||||
在 JDK 的开发流程中:**JEP** 通常用于重大的改变,例如引入新的语言特性、新的 JVM 机制或者大规模的库重构。像 `List.of()` 这种在现有类中增加几个工厂方法的操作,通常被视为常规的库维护。它们由 JDK 开发者直接通过 **JBS (JDK Bug System)** 的工单(Ticket)进行提交和评审,然后随版本直接发布。
|
||||
|
||||
### 集合增强
|
||||
|
||||
增加了 `List.of()`、`Set.of()`、`Map.of()` 和 `Map.ofEntries()` 等工厂方法来创建不可变集合(有点参考 Guava 的味道):
|
||||
|
||||
```java
|
||||
List.of("Java", "C++");
|
||||
Set.of("Java", "C++");
|
||||
Map.of("Java", 1, "C++", 2);
|
||||
```
|
||||
|
||||
使用 `of()` 创建的集合为不可变集合,不能进行添加、删除、替换、 排序等操作,不然会报 `java.lang.UnsupportedOperationException` 异常。
|
||||
|
||||
### Stream 增强
|
||||
|
||||
`Stream` 中增加了新的方法 `ofNullable()`、`dropWhile()`、`takeWhile()` 以及 `iterate()` 方法的重载方法。
|
||||
|
||||
Java 9 中的 `ofNullable()` 方 法允许我们创建一个单元素的 `Stream`,可以包含一个非空元素,也可以创建一个空 `Stream`。 而在 Java 8 中则不可以创建空的 `Stream`。
|
||||
|
||||
```java
|
||||
Stream<String> stringStream = Stream.ofNullable("Java");
|
||||
System.out.println(stringStream.count());// 1
|
||||
Stream<String> nullStream = Stream.ofNullable(null);
|
||||
System.out.println(nullStream.count());//0
|
||||
```
|
||||
|
||||
`takeWhile()` 方法可以从 `Stream` 中依次获取满足条件的元素,直到不满足条件为止结束获取。
|
||||
|
||||
```java
|
||||
List<Integer> integerList = List.of(11, 33, 66, 8, 9, 13);
|
||||
integerList.stream().takeWhile(x -> x < 50).forEach(System.out::println);// 11 33
|
||||
```
|
||||
|
||||
`dropWhile()` 方法的效果和 `takeWhile()` 相反。
|
||||
|
||||
```java
|
||||
List<Integer> integerList2 = List.of(11, 33, 66, 8, 9, 13);
|
||||
integerList2.stream().dropWhile(x -> x < 50).forEach(System.out::println);// 66 8 9 13
|
||||
```
|
||||
|
||||
`iterate()` 方法的新重载方法提供了一个 `Predicate` 参数(判断条件)来决定什么时候结束迭代
|
||||
|
||||
```java
|
||||
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
|
||||
}
|
||||
// 新增加的重载方法
|
||||
public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next) {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
两者的使用对比如下,新的 `iterate()` 重载方法更加灵活一些。
|
||||
|
||||
```java
|
||||
// 使用原始 iterate() 方法输出数字 1~10
|
||||
Stream.iterate(1, i -> i + 1).limit(10).forEach(System.out::println);
|
||||
// 使用新的 iterate() 重载方法输出数字 1~10
|
||||
Stream.iterate(1, i -> i <= 10, i -> i + 1).forEach(System.out::println);
|
||||
```
|
||||
|
||||
### Optional 增强
|
||||
|
||||
`Optional` 类中新增了 `ifPresentOrElse()`、`or()` 和 `stream()` 等方法
|
||||
|
||||
`ifPresentOrElse()` 方法接受两个参数 `Consumer` 和 `Runnable`,如果 `Optional` 不为空调用 `Consumer` 参数,为空则调用 `Runnable` 参数。
|
||||
|
||||
```java
|
||||
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
|
||||
|
||||
Optional<Object> objectOptional = Optional.empty();
|
||||
objectOptional.ifPresentOrElse(System.out::println, () -> System.out.println("Empty!!!"));// Empty!!!
|
||||
```
|
||||
|
||||
`or()` 方法接受一个 `Supplier` 参数,如果 `Optional` 为空则返回 `Supplier` 参数指定的 `Optional` 值。
|
||||
|
||||
```java
|
||||
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)
|
||||
|
||||
Optional<Object> objectOptional = Optional.empty();
|
||||
objectOptional.or(() -> Optional.of("java")).ifPresent(System.out::println);//java
|
||||
```
|
||||
|
||||
### String 增强
|
||||
|
||||
Java 8 及之前的版本,`String` 一直是用 `char[]` 存储。在 Java 9 之后,`String` 的实现改用 `byte[]` 数组存储字符串,节省了空间。
|
||||
|
||||
```java
|
||||
public final class String implements java.io.Serializable,Comparable<String>, CharSequence {
|
||||
// @Stable 注解表示变量最多被修改一次,称为"稳定的"。
|
||||
@Stable
|
||||
private final byte[] value;
|
||||
}
|
||||
```
|
||||
|
||||
### 接口增强
|
||||
|
||||
Java 9 允许在接口中使用私有方法。这样的话,接口的使用就更加灵活了,有点像是一个简化版的抽象类。
|
||||
|
||||
```java
|
||||
public interface MyInterface {
|
||||
private void methodPrivate(){
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### IO 增强
|
||||
|
||||
在 Java 9 之前,我们只能在 `try-with-resources` 块中声明变量:
|
||||
|
||||
```java
|
||||
try (Scanner scanner = new Scanner(new File("testRead.txt"));
|
||||
PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {
|
||||
// omitted
|
||||
}
|
||||
```
|
||||
|
||||
在 Java 9 之后,在 `try-with-resources` 语句中可以使用 effectively-final 变量。
|
||||
|
||||
```java
|
||||
final Scanner scanner = new Scanner(new File("testRead.txt"));
|
||||
PrintWriter writer = new PrintWriter(new File("testWrite.txt"));
|
||||
try (scanner; writer) {
|
||||
// omitted
|
||||
}
|
||||
```
|
||||
|
||||
**什么是 effectively-final 变量?** 简单来说就是没有被 `final` 修饰但是值在初始化后从未更改的变量。
|
||||
|
||||
正如上面的代码所演示的那样,即使 `writer` 变量没有被显示声明为 `final`,但它在第一次被赋值后就不会改变了,因此,它就是 effectively-final 变量。
|
||||
|
||||
### 进程 API
|
||||
|
||||
Java 9 增加了 `java.lang.ProcessHandle` 接口来实现对原生进程进行管理,尤其适合于管理长时间运行的进程。
|
||||
|
||||
```java
|
||||
// 获取当前正在运行的 JVM 的进程
|
||||
ProcessHandle currentProcess = ProcessHandle.current();
|
||||
// 输出进程的 id
|
||||
System.out.println(currentProcess.pid());
|
||||
// 输出进程的信息
|
||||
System.out.println(currentProcess.info());
|
||||
```
|
||||
|
||||
`ProcessHandle` 接口概览:
|
||||
|
||||

|
||||
|
||||
### 其他 API 增强
|
||||
|
||||
**响应式流(Reactive Streams)**
|
||||
|
||||
在 Java 9 中的 `java.util.concurrent.Flow` 类中新增了反应式流规范的核心接口。
|
||||
|
||||
`Flow` 中包含了 `Flow.Publisher`、`Flow.Subscriber`、`Flow.Subscription` 和 `Flow.Processor` 等 4 个核心接口。Java 9 还提供了 `SubmissionPublisher` 作为 `Flow.Publisher` 的一个实现。
|
||||
|
||||
关于 Java 9 响应式流更详细的解读,推荐你看 [Java 9 揭秘(17. Reactive Streams )- 林本托](https://www.cnblogs.com/IcanFixIt/p/7245377.html) 这篇文章。
|
||||
|
||||
## 其它
|
||||
|
||||
- **平台日志 API 改进**:Java 9 允许为 JDK 和应用配置同样的日志实现。新增了 `System.LoggerFinder` 用来管理 JDK 使 用的日志记录器实现。JVM 在运行时只有一个系统范围的 `LoggerFinder` 实例。我们可以通过添加自己的 `System.LoggerFinder` 实现来让 JDK 和应用使用 SLF4J 等其他日志记录框架。
|
||||
- **`CompletableFuture` 类增强**:新增了几个新的方法(`completeAsync`,`orTimeout` 等)。
|
||||
- **Nashorn 引擎的增强**:Nashorn 是从 Java8 开始引入的 JavaScript 引擎,Java9 对 Nashorn 做了些增强,实现了一些 ES6 的新特性(Java 11 中已经被弃用)。
|
||||
- **I/O 流的新特性**:增加了新的方法来读取和复制 `InputStream` 中包含的数据。
|
||||
- **改进应用的安全性能**:Java 9 新增了 4 个 SHA- 3 哈希算法,SHA3-224、SHA3-256、SHA3-384 和 SHA3-512。
|
||||
- **改进方法句柄(Method Handle)**:方法句柄从 Java7 开始引入,Java9 在类 `java.lang.invoke.MethodHandles` 中新增了更多的静态方法来创建不同类型的方法句柄。
|
||||
- ……
|
||||
|
||||
## 参考
|
||||
|
||||
- Java version history:<https://en.wikipedia.org/wiki/Java_version_history>
|
||||
- Release Notes for JDK 9 and JDK 9 Update Releases : <https://www.oracle.com/java/technologies/javase/9-all-relnotes.html>
|
||||
- 《深入剖析 Java 新特性》-极客时间 - JShell:怎么快速验证简单的小问题?
|
||||
- New Features in Java 9: <https://www.baeldung.com/new-java-9>
|
||||
- Java – Try with Resources:<https://www.baeldung.com/java-try-with-resources>
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
Reference in New Issue
Block a user