一文详解|从JDK8飞升到JDK17,再到未来的JDK21
背景
2022年Spring6和 SpringBoot3相继 推出 ,在此之前,Java社区一直是"新版任你发,我用Java 8",不管新版本怎么出,很少有人愿意升级。 这一次,Spring 直接来了个大招,SpringBoot3和Spring6的最低依赖就是JDK17!跨过 JDK 8-16,直接升级到 JDK 17。那么为什么是 JDK 17呢?
为什么是JDK17
这么多新版本的 JDK,而且2022年还会推出 JDK 18 和 JDK 19,为什么 Spring 选择了 JDK 17呢。
主要是因为他是一个 Oracle官宣可以免费商用的LTS版本,所谓 LTS,是 Long Term Support,也就是官方保证会长期支持的版本。
从JDK8到JDK17的新特性
- 模块化
- 提供了List.of()、Set.of()、Map.of()和Map.ofEntries()等工厂方法
- 接口支持私有方法
- Optional 类改进
- 多版本兼容Jar包
- JShell工具
- try-with-resources的改进
- Stream API的改进
- 设置G1为JVM默认垃圾收集器
- 支持http2.0和websocket的API
- 局部变量类型推断,类似JS可以通过var来修饰局部变量,编译之后会推断出值的真实类型
- 不可变集合的改进
- 并行全垃圾回收器 G1,来优化G1的延迟
- 线程本地握手,允许在不执行全局VM安全点的情况下执行线程回调,可以停止单个线程,而不需要停止所有线程或不停止线程
- Optional新增orElseThrow()方法
- 类数据共享
- Unicode 语言标签扩展
- 根证书
- 增加一些字符串处理方法
- 用于 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进行增强
JDK12新特性(2019年3月)
- Switch 表达式扩展,可以有返回值
- 新增NumberFormat对复杂数字的格式化
- 字符串支持transform、indent操作
- 新增方法Files.mismatch(Path, Path)
- Teeing Collector
- 支持unicode 11
- Shenandoah GC,新增的GC算法
- G1收集器的优化,将GC的垃圾分为强制部分和可选部分,强制部分会被回收,可选部分可能不会被回收,提高GC的效率
- Switch 表达式扩展,switch表达式增加yield关键字用于返回结果,作用类似于return,如果没有返回结果则使用break
- 文本块升级 """ ,引入了文本块,可以使用"""三个双引号表示文本块,文本块内部就不需要使用换行的转义字符
- SocketAPI 重构,Socket的底层实现优化,引入了NIO
- FileSystems.newFileSystem新方法
- ZGC优化,增强 ZGC 释放未使用内存,将标记长时间空闲的堆内存空间返还给操作系统,保证堆大小不会小于配置的最小堆内存大小,如果堆最大和最小内存大小设置一样,则不会释放内存还给操作系统
- instanceof模式匹配,instanceof类型匹配语法简化,可以直接给对象赋值,如if(obj instanceof String str),如果obj是字符串类型则直接赋值给了str变量
- 引入Record类型,类似于Lombok 的@Data注解,可以向Lombok一样自动生成构造器、equals、getter等方法;
- Switch 表达式-标准化
- 改进 NullPointerExceptions提示信息,打印具体哪个方法抛的空指针异常,避免同一行代码多个函数调用时无法判断具体是哪个函数抛异常的困扰,方便异常排查;
- 删除 CMS 垃圾回收器
- EdDSA 数字签名算法
- Sealed Classes(封闭类,预览),通过sealed关键字修饰抽象类限定只允许指定的子类才可以实现或继承抽象类,避免抽象类被滥用
- Hidden Classes(隐藏类)
- 移除 Nashorn JavaScript引擎
- 改进java.net.DatagramSocket 和 java.net.MulticastSocket底层实现
- 允许在 JDK C ++源代码中使用 C ++ 14功能
- ZGC性能优化,去掉ZGC线程堆栈处理从安全点到并发阶段
- 增加 Unix 域套接字通道
- 弹性元空间能力
- 提供用于打包独立 Java 应用程序的 jpackage 工具
- Free Java License
- JDK 17 将取代 JDK 11 成为下一个长期支持版本
- Spring 6 和 Spring Boot 3需要JDK17
- 移除实验性的 AOT 和 JIT 编译器
- 恢复始终执行严格模式 (Always-Strict) 的浮点定义
- 正式引入密封类sealed class,限制抽象类的实现
- 统一日志异步刷新,先将日志写入缓存,然后再异步刷新
重要特性详解
-
一个模块通常只是一个 jar 文件,在根目录下有一个文件 module-info.class 。
-
要使用模块,请将 jar 文件包含到 modulepath 而不是 classpath . 添加到类路径的模块化 jar 文件是普通的 jar 文件, module-info.class 文件将被忽略。
module helloworld {exports com.alibaba.eight;}module test {requires helloworld;}
在Java 10中,提供了本地变量类型推断的功能,可以通过var声明变量:MyObject value = new MyObject();
本地变量类型推断将引入“var”关键字,而不需要显式的规范变量的类型。 其实,所谓的本地变量类型推断,也是Java 10提供给开发者的语法糖。 虽然我们在代码中使用var进行了定义,但是对于虚拟机来说他是不认识这个var的,在java文件编译成class文件的过程中,会进行解糖,使用变量真正的类型来替代varvar value = new MyObject();
- 完整支持HTTP 2.0 或者HTTP 1.1
- 支持 HTTPS/TLS
- 有简单的阻塞使用方法
- 支持异步发送,异步时间通知
- 支持WebSocket
- 支持响应式流
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);
添加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();
Switch表达式改进
支持箭头表达式(jdk12预览 jdk14标准)
此更改扩展了switch 语句以便它可以用作语句或表达式。 不必为break每个 case 块定义一个语句,我们可以简单地使用 箭头语法。
yield关键字(jdk13) 使用yield,我们现在可以有效地从 switch 表达式返回值,并能够更容易实现策略模式。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);//要使用此预览功能,我们必须在应用程序启动期间使用–enable-preview标志明确指示 JVM。
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);}}
字符串
文本块改进(jdk13)
早些时候,为了在我们的代码中嵌入 JSON,我们将其声明为字符串文字:
String json = "{\r\n" + "\"name\" : \"lingli\",\r\n" + "\"website\" : \"https://www.alibaba.com/\"\r\n" + "}";现在让我们使用字符串文本块编写相同的 JSON :
很明显,不需要转义双引号或添加回车。通过使用文本块,嵌入的 JSON 更易于编写,更易于阅读和维护。String json = """{"name" : "Baeldung","website" : "https://www.alibaba.com/"}""";
更多的API
- isBlank():如果字符串为空或字符串仅包含空格(包括制表符),则返回 true。注意与isEmpty() 不同,isEmpty()仅在长度为 0 时返回 true。
- lines():将字符串拆分为字符串流,每个字符串包含一行。
- strip() :分别从开头和结尾;
- stripLeading()/stripTrailing()仅开始和仅结束删除空格。
- repeat(int times):返回一个字符串,该字符串采用原始字符串并按指定的次数重复该字符串。
- readString():允许从文件路径直接读取到字符串。
- writeString(Path path):将字符串直接写入指定路径处的文件。
- indent(int level):缩进字符串的指定量。负值只会影响前导空格。
- transform(Function f):将给定的 lambda 应用于字符串。
instanceof 的模式匹配(jdk14出预览,jdk16最终确认)
之前:Object obj = "大阳";if (obj instanceof String) {String t = (String) obj;// TODO}现在:Object obj = "大阳";if (obj instanceof String t) {// TODO 此时t已经是String类型了}
record记录类(jdk16正式)
传统的Java应用程序通过创建一个类,通过该类的构造方法实例化类,并通过getter和setter方法访问成员变量或者设置成员变量的值。有了record关键字,你的代码会变得更加简洁。/*** record 记录类* 你也可以覆写equals() hashCode() toString()方法,不用写get、set了* @author DAYANG*/record User(String name, Integer age) {@Overridepublic String toString() {return "User[" +"name='" + name + '\'' +", age=" + age +']';}@Overridepublic boolean equals(Object obj) {return false;}@Overridepublic int hashCode() {return 0;}}
JVM
GC变化
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毫秒指标测试
吞吐量比较
延迟比较
暂停时间对比
资源占用
密封类和接口(15预览 17正式)
在Java15之前,所有的类都可以没有限制地继承其他类--除非被继承类被声明为final类型,任何类都可以实现公共接口。 现在在Java15中,一个类或者接口可以使用修饰符sealed声明为密封类或者接口,来限制其继承类。/*** 定义一个抽象密封类Pet,它的实现类只能是Dog, Cat这两个,其他的实现类均不允许*/public abstract sealed class Petpermits Dog, Cat {}final class Dog extends Pet {}final class Cat extends Pet {}//密封的类和接口限制了其他类或接口可以扩展或实现它们public sealed interface Shape{final class Planet implements Shape {}final class Star implements Shape {}final class Comet implements Shape {}}public abstract sealed class Test{final class A extends Test {}final class B extends Test {}final class C extends Test {}}
EdDSA 算法
EdDSA (Edwards-Curve Digital Signature Algorithm) 是在 Java 15 中通过JEP 339添加的另一种附加数字签名方案。与其他可用的签名方案相比,它提供了更好的性能和安全的签名。总结和展望