# 一.JDK 新特性
# 1.汇总
JDK9-JDK17 中几个关键的新特性:
- sealed 密封类
- 文本块
- record 纪录类
- G1 成为默认垃圾收集器
- ZGC 的完善与升级
- JDK 模块化
- JFR 飞行器
- 新 swich 表达式
- 虚拟线程
# 2.JDK9 新特性
JDK9 新特性(2017 年 9 月)
- 模块化
- 提供了 List.of()、Set.of()、Map.of()和 Map.ofEntries()等工厂方法
- 接口支持私有方法
- Optional 类改进
- 多版本兼容 Jar 包
- JShell 工具
- try-with-resources 的改进
- Stream API 的改进
- 设置 G1 为 JVM 默认垃圾收集器
- 支持 http2.0 和 websocket 的 API
重要特性:主要是 API 的优化,如支持 HTTP2 的 Client API、JVM 采用 G1 为默认垃圾收集器。
# 3.JDK10 新特性
JDK10 新特性(2018 年 3 月)
- 局部变量类型推断,类似 JS 可以通过 var 来修饰局部变量,编译之后会推断出值的真实类型
- 不可变集合的改进
- 并行全垃圾回收器 G1,来优化 G1 的延迟
- 线程本地握手,允许在不执行全局 VM 安全点的情况下执行线程回调,可以停止单个线程,而不需要停止所有线程或不停止线程
- Optional 新增 orElseThrow()方法
- 类数据共享
- Unicode 语言标签扩展
- 根证书
重要特性:通过 var 关键字实现局部变量类型推断,使 Java 语言变成弱类型语言、JVM 的 G1 垃圾回收由单线程改成多线程并行处理,降低 G1 的停顿时间。
# 4.JDK11 新特性
JDK11 新特性(2018 年 9 月)(LTS 版本)
- 增加一些字符串处理方法
- 用于 Lambda 参数的局部变量语法
- Http Client 重写,支持 HTTP/1.1 和 HTTP/2 ,也支持 websockets
- 可运行单一 Java 源码文件,如:java Test.java
- ZGC:可伸缩低延迟垃圾收集器,ZGC 可以看做是 G1 之上更细粒度的内存管理策略。由于内存的不断分配回收会产生大量的内存碎片空间,因此需要整理策略防止内存空间碎片化,在整理期间需要将对于内存引用的线程逻辑暂停,这个过程被称为"Stop the world"。只有当整理完成后,线程逻辑才可以继续运行。(并行回收)
- 支持 TLS 1.3 协议
- Flight Recorder(飞行记录器),基于 OS、JVM 和 JDK 的事件产生的数据收集框架
- 对 Stream、Optional、集合 API 进行增强
重要特性:对于 JDK9 和 JDK10 的完善,主要是对于 Stream、集合等 API 的增强、新增 ZGC 垃圾收集器。
# 5.JDK12 新特性
JDK12 新特性(2019 年 3 月)
- Switch 表达式扩展,可以有返回值
- 新增 NumberFormat 对复杂数字的格式化
- 字符串支持 transform、indent 操作
- 新增方法 Files.mismatch(Path, Path)
- Teeing Collector
- 支持 unicode 11
- Shenandoah GC,新增的 GC 算法
- G1 收集器的优化,将 GC 的垃圾分为强制部分和可选部分,强制部分会被回收,可选部分可能不会被回收,提高 GC 的效率
重要特性:switch 表达式语法扩展、G1 收集器优化、新增 Shenandoah GC 垃圾回收算法。
# 6.JDK13 新特性
JDK13 新特性(2019 年 9 月)
- Switch 表达式扩展,switch 表达式增加 yield 关键字用于返回结果,作用类似于 return,如果没有返回结果则使用 break
- 文本块升级 """ ,引入了文本块,可以使用"""三个双引号表示文本块,文本块内部就不需要使用换行的转义字符
- SocketAPI 重构,Socket 的底层实现优化,引入了 NIO
- FileSystems.newFileSystem 新方法
- ZGC 优化,增强 ZGC 释放未使用内存,将标记长时间空闲的堆内存空间返还给操作系统,保证堆大小不会小于配置的最小堆内存大小,如果堆最大和最小内存大小设置一样,则不会释放内存还给操作系统
重要特性:ZGC 优化,释放内存还给操作系统、socket 底层实现引入 NIO。
# 7.JDK14 新特性
JDK14 新特性(2020 年 3 月)
- instanceof 模式匹配,instanceof 类型匹配语法简化,可以直接给对象赋值,如 if(obj instanceof String str),如果 obj 是字符串类型则直接赋值给了 str 变量
- 引入 Record 类型,类似于 Lombok 的@Data 注解,可以向 Lombok 一样自动生成构造器、equals、getter 等方法;
- Switch 表达式-标准化
- 改进 NullPointerExceptions 提示信息,打印具体哪个方法抛的空指针异常,避免同一行代码多个函数调用时无法判断具体是哪个函数抛异常的困扰,方便异常排查;
- 删除 CMS 垃圾回收器
# 8.JDK15 新特性
JDK15 新特性(2020 年 9 月)
- EdDSA 数字签名算法
- Sealed Classes(封闭类,预览),通过 sealed 关键字修饰抽象类限定只允许指定的子类才可以实现或继承抽象类,避免抽象类被滥用
- Hidden Classes(隐藏类)
- 移除 Nashorn JavaScript 引擎
- 改进 java.net.DatagramSocket 和 java.net.MulticastSocket 底层实现
# 9.JDK16 新特性
JDK16 新特性(2021 年 3 月)
- 允许在 JDK C ++源代码中使用 C ++ 14 功能
- ZGC 性能优化,去掉 ZGC 线程堆栈处理从安全点到并发阶段
- 增加 Unix 域套接字通道
- 弹性元空间能力
- 提供用于打包独立 Java 应用程序的 jpackage 工具
JDK16 相当于是将 JDK14、JDK15 的一些特性进行了正式引入,如 instanceof 模式匹配(Pattern matching)、record 的引入等最终到 JDK16 变成了 final 版本。
# 10.JDK17 新特性
JDK17 新特性(2021 年 9 月)(LTS 版本)
- Free Java License 免费长期支持版本
- JDK 17 将取代 JDK 11 成为下一个长期支持版本
- Spring 6 和 Spring Boot 3 需要 JDK17
- 移除实验性的 AOT 和 JIT 编译器
- 恢复始终执行严格模式 (Always-Strict) 的浮点定义
- 正式引入密封类 sealed class,限制抽象类的实现
- 统一日志异步刷新,先将日志写入缓存,然后再异步刷新
虽然 JDK17 也是一个 LTS 版本,但是并没有像 JDK8 和 JDK11 一样引入比较突出的特性,主要是对前几个版本的整合和完善。
# 11.JDK21 新特性
- 序列集合
- 分代 ZGC
- 记录模式
- switch 模式匹配
- 虚拟线程
- 弃用 Windows 32 位 x86 移植
- 不允许动态加载代理
- 密钥封装机制
# 二.重要特性讲解
它发认它发,我用 Java8
# 1.模块化
**模块化的目的,是让 jdk 的各个组件可以被分拆,复用和替换重写,**比如对 java 的 gui 不满意,可以自己实现一个 gui,对 java 的语法不满意,可以把 javac 替换成其他语言和其他语言的编译器,比如 kotlin 和 kotlinc 等,没有模块化,几乎很难实现,每次修改某个模块,总不能把整个 jdk 给重新编译一遍,再发布一个整个 sdk 吧,模块化可以帮助更有效的定制化和部署
# 2.变量类型推断
在 Java 10 中,提供了本地变量类型推断的功能,可以通过 var 声明变量:
var value = new MyObject();
# 3.HTTP 客户端 API
作为 JDK11 中正式推出的新 Http 连接器,支持的功能还是比较新的,主要的特性有:
- 完整支持 HTTP 2.0 或者 HTTP 1.1
- 支持 HTTPS/TLS
- 有简单的阻塞使用方法
- 支持异步发送,异步时间通知
- 支持 WebSocket
- 支持响应式流
# 4.语法糖
Stream API 改进:
Collectors.teeing()
teeing 收集器已公开为静态方法 Collectors::teeing。该收集器将其输入转发给其他两个收集器,然后将它们的结果使用函数合并
List<Student> list = Arrays.asList(
new Student("唐一", 55),
new Student("唐二", 60),
new Student("唐三", 90));
//平均分 总分
String result = list.stream().collect(Collectors.teeing(
Collectors.averagingInt(Student::getScore),
Collectors.summingInt(Student::getScore),
(s1, s2) -> s1 + ":" + s2));
//最低分 最高分
String result2 = list.stream().collect(Collectors.teeing(
Collectors.minBy(Comparator.comparing(Student::getScore)),
Collectors.maxBy(Comparator.comparing(Student::getScore)),
(s1, s2) -> s1.orElseThrow() + ":" + s2.orElseThrow()
));
System.out.println(result);
System.out.println(result2);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
添加 Stream.toList 方法(jdk16)
List<String> list = Arrays.asList("1", "2", "3");
//之前这样写
List<Integer> oneList = list.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
//现在可以这样写
List<Integer> twoList = list.stream()
.map(Integer::parseInt)
.toList();
2
3
4
5
6
7
8
9
10
Switch 表达式改进:
支持箭头表达式(jdk12 预览 jdk14 标准)
此更改扩展了 switch 语句以便它可以用作语句或表达式。不必为 break 每个 case 块定义一个语句,我们可以简单地使用箭头语法。
boolean isWeekend = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> false;
case SATURDAY, SUNDAY -> true;
default -> throw new IllegalStateException("Illegal day entry :: " + day);
};
int size = 3;
String cn = switch (size) {
case 1 -> "壹";
case 2 -> "贰";
case 3, 4 -> "叁";
default -> "未知";
};
System.out.println(cn);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
yield 关键字(jdk13):
使用 yield,我们现在可以有效地从 switch 表达式返回值,并能够更容易实现策略模式。
return:在程序函数中返回某个值,返回之后函数不在继续执行,彻底结束。
yield: 带有yield的函数是一个迭代器,函数返回某个值时,会停留在某个位置,返回函数值后,会在前面停留的位置继续执行,直到程序结束
public class SwitchTest {
public static void main(String[] args) {
var me = 4;
var operation = "平方";
var result = switch (operation) {
case "加倍" -> {
yield me * 2;
}
case "平方" -> {
yield me * me;
}
default -> me;
};
System.out.println(result);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
例子:
def foo():
print("starting...")
while True:
res = yield 4
print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))
2
3
4
5
6
7
8
9
starting...
4
********************
res: None
4
2
3
4
5
1.程序开始执行以后,因为foo函数中有yield关键字,所以foo函数并不会真的执行,而是先得到一个生成器g(相当于一个对象)
2.直到调用next方法,foo函数正式开始执行,先执行foo函数中的print方法,然后进入while循环
3.程序遇到yield关键字,然后把yield想想成return,return了一个4之后,程序停止,并没有执行赋值给res操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while上面的print的结果,第二个是return出的结果)是执行print(next(g))的结果,
4.程序执行 print("*"20),输出 20 个
5.又开始执行下面的print(next(g)),这个时候和上面那个差不多,不过不同的是,这个时候是从刚才那个next程序停止的地方开始执行的,也就是要执行res的赋值操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,并没有给赋值操作的左边传参数),所以这个时候res赋值是None,所以接着下面的输出就是res:None,
6.程序会继续在while里执行,又一次碰到yield,这个时候同样return 出4,然后程序停止,print函数输出的4就是这次return出的4.
字符串:
文本块改进(jdk13)
早些时候,为了在我们的代码中嵌入 JSON,我们将其声明为字符串文字:
String json = "{\r\n" + "\"name\" : \"lingli\",\r\n" + "\"website\" : \"https://www.alibaba.com/\"\r\n" + "}";
现在让我们使用字符串文本块编写相同的 JSON :
String json = """ {
"name" : "Baeldung",
"website" : "https://www.alibaba.com/"
} """;
2
3
4
很明显,不需要转义双引号或添加回车。通过使用文本块,嵌入的 JSON 更易于编写,更易于阅读和维护。
# 5.JVM
JDK9: 设置 G1 为 JVM 默认垃圾收集器
JDK10:并行全垃圾回收器 G1,通过并行 Full GC, 改善 G1 的延迟。目前对 G1 的 full GC 的实现采用了单线程-清除-压缩算法。JDK10 开始使用并行化-清除-压缩算法。
JDK11:推出 ZGC 新一代垃圾回收器(实验性),目标是 GC 暂停时间不会超过 10ms,既能处理几百兆的小堆,也能处理几个 T 的大堆。
JDK14 :删除 CMS 垃圾回收器;弃用 ParallelScavenge + SerialOld GC 的垃圾回收算法组合;将 zgc 垃圾回收器移植到 macOS 和 windows 平台
JDk 15 : ZGC (JEP 377) 和Shenandoah (JEP 379) 不再是实验性功能。默认的 GC 仍然是G1。
JDK16:增强 ZGC,ZGC 获得了 46 个增强功能 和 25 个错误修复,控制 stw 时间不超过 10 毫秒
# 三.详解
# 1.String 底层优化
在 jdk9 中 String 类的源码已经由 char[] 优化为了 byte[] 来存储字符串内容,为什么要这样做呢?
在 Java 9 之前,String 类的内部实现是基于 char[]数组的,这意味着每个字符都需要占用两个字节的存储空间。然而,对于大多数字符集来说,只需要使用一个字节的存储空间即可表示一个字符。
为了解决这个问题,Java 9 中对 String 类进行了优化,将内部实现从 char[]数组改为了 byte[]数组。这样,在使用大多数字符集时,每个字符只需要占用一个字节的存储空间,这样可以节省大量的内存空间。
使用 char[]
来表示 String 就导致了即使 String 中的字符只用一个字节就能表示,也得占用两个字节。
同时,这种改变还可以带来其他好处,例如在处理大型文本文件时,使用 byte[]数组可以更快地读取和处理数据,因为它们需要更少的内存和 CPU 资源。
将 String 类的内部实现从 char[]数组改为 byte[]数组是一种更加高效和优化的做法,可以提高程序的性能和效率。
# 四.JDK21
# 1.介绍
JDK 21 已经于 2023 年 9 月 19 日正式发布。本文总结了 JDK 21 发布的新特性。
根据发布的规划,这次发布的 JDK 21 将是一个长期支持版(LTS 版)。LTS 版每 2 年发布一个,上一次长期支持版是 21 年 9 月发布的 JDK 17。不能抱有你强任你强,我用 java8 的思想。
# 2.版本
主要分为 OpenJDK 版本和 Oracle 版本,下载地址如下:
- OpenJDK 版本:https://jdk.java.net/21/ (opens new window)
- Oracle 版本:https://www.oracle.com/java/technologies/downloads/ (opens new window)
# 3.JDK21 新特性
- 序列集合
- 分代 ZGC
- 记录模式
- switch 模式匹配
- 虚拟线程
- 弃用 Windows 32 位 x86 移植
- 不允许动态加载代理
- 密钥封装机制
# 4.安装 jdk21
# 5.虚拟线程
将虚拟线程(Virtual Threads)引入 Java 平台。虚拟线程是轻量级线程,可以显著减少编写、维护和观察高吞吐量并发应用程序的工作量。
- 轻量级线程管理:虚拟线程不需要底层操作系统线程的支持,因此可以创建数千甚至数万个虚拟线程而不会消耗大量的内存和资源。这使得应用程序可以更高效地管理大量并发任务。
- 更快的线程创建和销毁:传统的 Java 线程创建和销毁通常涉及昂贵的操作系统调用,而虚拟线程的创建和销毁成本更低,因此可以更快速地启动和停止线程。
- 更好的资源利用:由于虚拟线程可以更轻松地伸缩,因此它们有助于更好地利用现有的系统资源,以处理大规模的并发请求。
- 避免死锁和资源争夺:虚拟线程的管理方式可以减少线程之间的竞争和资源争夺,从而降低了死锁和性能问题的风险。
- 简化并发编程:虚拟线程的引入使得编写并发程序变得更加容易,开发人员可以专注于业务逻辑而不必过多关注线程管理和同步。
public class Test01 {
public static void main(String[] args) {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10000).forEach(i -> executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
}));
}
}
}
2
3
4
5
6
7
8
9
10
# 6.有序集合
在 JDK 21 中,Sequenced Collections 的引入带来了新的接口和方法来简化集合处理。此增强功能旨在解决访问 Java 中各种集合类型的第一个和最后一个元素需要非统一且麻烦处理场景。
Sequenced Collections 引入了三个新接口:
- SequencedCollection
- SequencedMap
- SequencedSet
绿色方框是新增的 3 个接口,从中可以看到已有集合类的继承关系的变化:
List 继承自 SequencedCollection。
Deque 继承自 SequencedCollection。
LinkedHashSet 实现了 SequencedSet 接口。
SortedSet 继承自 SequencedSet。
LinkedHashMap 实现了 SequencedMap 接口。
SortedMap 继承自 SequencedMap。
有了这 3 个新的顺序集合相关的接口之后,Java 代码可以更清楚地表达顺序集合以及顺序集合上的操作。
这些接口附带了一些新方法,以提供改进的集合访问和操作功能。
/**
* 序列集合
*
* @author : qinyingjie
* @version : 2.2.0
* @date : 2023/9/26 01:37
*/
public class Test02 {
public static void main(String[] args) {
SequencedCollection<Integer> arr = new ArrayList<>();
arr.addLast(1);
arr.addLast(2);
arr.addLast(3);
arr.addLast(31);
arr.addFirst(14);
System.out.println(arr);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
第一个和最后一个元素的访问:
在 JDK 21 之前,检索 Java 中集合的第一个和最后一个元素涉及不同的方法和途径,具体取决于集合类型。
下面让我们看一下使用 JDK 21 之前的 JDK API 调用访问第一个和最后一个元素的一些示例:
访问位置 | List | Deque | SortedSet |
---|---|---|---|
第一个元素 | list.get(0) | deque.getFirst() | set.first() |
最后一个元素 | list.get(list.size()-1) | deque.getLast() | set.last() |
可以看到,一个简单的操作,在不同的集合中需要不同的编写方式,非常麻烦!
但在 JDK 21 之后,访问第一个和最后一个元素就方法多了:
对于List
, Deque
, Set
这些有序的集合,访问方法变得统一起来:
- 第一个元素:
collection.getFirst()
- 最后一个元素:
collection.getLast()
首先是 SequencedCollection
,该接口的声明如下所示:SequencedCollection
继承自 Collection
。
interface SequencedCollection<E> extends Collection<E> {
SequencedCollection<E> reversed();
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}
2
3
4
5
6
7
8
9
在包含的方法中:
reversed 方法返回一个逆序的 SequencedCollection 对象。
addFirst 和 addLast 方法分别在集合的起始和结束位置添加新的元素。
getFirst 和 getLast 方法分别获取集合的第一个和最后一个元素。
removeFirst 和 removeLast 方法分别删除集合的第一个和最后一个元素。
SequencedSet
interface SequencedSet<E> extends Set<E>, SequencedCollection<E> {
SequencedSet<E> reversed();
}
2
3
SequencedMap
SequencedMap
继承自 Map
,其中的 entry 有确定的出现顺序。
SequencedMap
中的方法比较多,如下所示:
interface SequencedMap<K,V> extends Map<K,V> {
SequencedMap<K,V> reversed();
SequencedSet<K> sequencedKeySet();
SequencedCollection<V> sequencedValues();
SequencedSet<Entry<K,V>> sequencedEntrySet();
V putFirst(K, V);
V putLast(K, V);
Entry<K, V> firstEntry();
Entry<K, V> lastEntry();
Entry<K, V> pollFirstEntry();
Entry<K, V> pollLastEntry();
}
2
3
4
5
6
7
8
9
10
11
12
13
具体的方法说明:
reversed 方法返回一个 entry 逆序的 SequencedMap。
sequencedKeySet 方法返回包含 key 的 SequencedSet。
sequencedValues 方法返回包含 value 的 SequencedCollection。
sequencedEntrySet 方法返回包含 entry 的 SequencedSet。
putFirst 和 putLast 分别在 entry 的最前和最后位置插入名值对。
firstEntry 和 lastEntry 分别获取第一个和最后一个 entry。
pollFirstEntry 和 pollLastEntry 分别删除第一个和最后一个 entry。
# 7.记录模式
使用记录模式(Record Patterns)增强 Java 编程语言,以解构记录值。可以嵌套记录模式和类型模式,以实现功能强大、声明性和可组合形式的数据导航和处理。
/**
* 记录类
*
* @author : qinyingjie
* @date : 2023/9/26
*/
public record Test03(int x, int y) {
}
2
3
4
5
6
7
8
static void print(Object o) {
if (o instanceof Test03(int x, int y)) {
System.out.println(x + y);
}
}
2
3
4
5
# 8.switch 模式匹配
通过 switch 表达式和语句的模式匹配来增强 Java 编程语言。通过将模式匹配扩展到 switch,可以针对多个模式测试表达式,每个模式都有一个特定的操作,从而可以简洁、安全地表达复杂的面向数据的查询。
/**
* switch模式匹配
*
* @author : qinyingjie
* @version : 2.2.0
* @date : 2023/9/26 01:37
*/
public class Test04 {
public static void main(String[] args) {
Object obj = "你好";
Object a = switch (obj) {
case Integer i -> String.format("int %d", i);
case String s -> String.format("string %s", s);
case Double d -> String.format("Double %s", d);
default -> obj.toString();
};
System.out.println(a);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19