大兄弟们,周五了,赶紧看篇文章摸会鱼,帮忙对文章提提意见
本文依赖 openjdk 官方文档以及博客《Java8+特性知识体系详解》对JDK9对JDK17的新特性进行了整理。我在此文中主要是将一些晦涩的特性说明,进行了一些资料查证,转换为易懂的说明,同时增添一些测试用例帮助理解。此文能帮助你对新特性有个初入门的了解,具体的还需要亲身验证学习深入
`
前言
2017年9月后,Java 发布了 Java9 版本,至2023年9月,已经累计发布了13 个版本,对比 Java8 之前几年一个版本的迭代周期,这个发布速度无疑加快了很多。
另外在2023年4月20日,New Relic 最新发布了一份 “2023 年 Java 生态系统状况报告”。报告中显示 56% 的应用程序在生产中使用了 Java11 版本(高于2022年的 48% 与 2020年的 11% ),Java8 紧随其后,采用比例为33%(低于2022年的 46%)。
2023年,尽管 Java8 与 Java11 的采用率合计达到了 89%,但是 Java17 的的采用率却表现的足够亮眼,远远超过了Java11 出现时的情形,Java17 的采用率达到了 9% (较去年的不到1%,提升了8%)。
基于此,本次分享介绍下Java的版本更新周期以及 Java9 至 Java17 版本新特性。
图-1:“2023 年 Java 生态系统状况报告”中近几年的 Java 版本使用情况
Java版本说明
从Java9开始,Java改变了之前以功能特性为导向的发布周期。而是转为固定时间间隔的火车发布模式(Release Train)。火车定时发车,赶不上这次车的乘客,就只能等下一班火车。Java的固定发布时间是每年的3月和9月。除了每年定期的两个版本之外,Java还引入了LTS(Long Term Support) 版本的概念。LTS表示长期支持版本,Ubuntu和NodeJS都有类似的概念。目前Java 8、Java 11、Java17是LTS版本。除了LTS版本之外的其他版本,都认为是在上一个LTS版本之上的小的功能改进。
两次发布的间隔只有6个月。对于一些改动比较大的功能来说,6个月的时间有些短了。因此,Java引入了预览功能的概念。一些改动会以预览功能的形式在某个版本加入,并在后续的版本中不断的更新,直到成为正式功能。当一个新功能在 Java 版本N中推出之后,就可以由开发人员来试用,并提供反馈。根据反馈的结果,该功能可以继续 Java 版本N+1中预览,直到 Java 版本 N+2 中稳定下来。比如,Record 记录类型,最早在 Java 14 中以预览功能的形式引入,经过 Java 15 的再次预览,在 Java 16 中成为正式功能。
版本 | 发布日期 | 最终免费公开更新时间 | 最后延伸支持日期 |
---|---|---|---|
JDK Beta | 1995 | ? | ? |
JDK 1.0 | 1996 年 1 月 | ? | ? |
JDK 1.1 | 1997 年 2 月 | ? | ? |
J2SE 1.2 | 1998 年 12 月 | ? | ? |
J2SE 1.3 | 2000 年 5 月 | ? | ? |
J2SE 1.4 | 2002 年 2 月 | 2008 年 10 月 | 2013 年 2 月 |
J2SE 5.0 | 2004 年 9 月 | 2009 年 11 月 | 2015 年 4 月 |
Java SE 6 | 2006 年 12 月 | 2013 年 4 月 | Oracle 于 2018 年 12 月停止更新 Azul 于 2026 年 12 月停止更新 |
Java SE 7 | 2011 年 7 月 | OpenJDK 于 2022 年 9 月停止更新(2015 年 5 月前由 Oracle 维护) Red Hat 于 2020 年 8 月停止更新 Azul 于 2022 年 9 月停止更新 | Oracle 于 2022 年 7 月停止更新 Red Hat 于 2020 年 6 月停止更新 Azul 于 2027 年 12 月停止更新 |
Java SE 8 (LTS) | 2014 年 3 月 | OpenJDK 目前由 Red Hat 维护 Oracle 于 2022 年 3 月停止更新(商用) Oracle 于 2030 年 12 月停止更新(非商用) Azul 于 2030 年 12 月停止更新 IBM Semeru 于 2026 年 5 月停止更新 Eclipse Adoptium 于 2026 年 5 月或之后停止更新 Amazon Corretto 于 2026 年 5 月或之后停止更新 | Oracle 于 2030 年 12 月停止更新 Red Hat 于 2026 年 11 月停止更新 |
Java SE 9 | 2017 年 9 月 | OpenJDK 于 2018 年 3 月停止更新 | 不适用 |
Java SE 10 | 2018 年 3 月 | OpenJDK 于 2018 年 9 月停止更新 | 不适用 |
Java SE 11 (LTS) | 2018 年 9 月 | OpenJDK 目前由 Red Hat 维护 Azul 于 2026 年 9 月停止更新 IBM Semeru 于 2024 年 10 月停止更新 Eclipse Adoptium 于 2024 年 10 月或之后停止更新 Amazon Corretto 于 2027 年 9 月或之后停止更新 微软于 2024 年 10 月或之后停止更新 | Oracle 于 2026 年 9 月停止更新 Azul 于 2026 年 9 月停止更新 Red Hat 于 2024 年 10 月停止更新 |
Java SE 12 | 2019 年 3 月 | OpenJDK 于 2019 年 9 月停止更新 | 不适用 |
Java SE 13 | 2019 年 9 月 | OpenJDK 目前由 Azul 维护 Azul 于 2023 年 3 月停止更新 | 不适用 |
Java SE 14 | 2020 年 3 月 | OpenJDK 于 2020 年 9 月停止更新 | 不适用 |
Java SE 15 | 2020 年 9 月 | OpenJDK 目前由 Azul 维护 Azul 于 2023 年 3 月停止更新 | 不适用 |
Java SE 16 | 2021 年 3 月 | OpenJDK 于 2021 年 9 月停止更新 | 不适用 |
Java SE 17 (LTS) | 2021 年 9 月 | OpenJDK 目前由 SAP 维护 Azul 于 2029 年 9 月停止更新 IBM Semeru 于 2027 年 10 月停止更新 微软于 2027 年 9 月或之后停止更新 Eclipse Adoptium 于 2027 年 9 月或之后停止更新 | Oracle 于 2029 年 9 月或之后停止更新 Azul 于 2029 年 9 月停止更新 Red Hat 于 2027 年 10 月停止更新 |
Java SE 18 | 2022 年 3 月 | OpenJDK 于 2022 年 9 月停止更新 Eclipse Adoptium 于 2022 年 9 月或之后停止更新 | 不适用 |
Java SE 19 | 2022 年 9 月 | OpenJDK 于 2023 年 3 月停止更新 | 不适用 |
Java SE 20 | 2023 年 3 月 | Oracle 于 2023 年 9 月停止更新 | 不适用 |
不同的Java发行版本
另外一个常见的困惑是为什么 Java 有这么多的发行版本,除了 OpenJDK 之外,还有 AdoptOpenJDK ?这其实也和 Java 版本更新方式的变化有关。
OpenJDK是一个开源项目,源代码就在GitHub上。但是对一般用户来说,需要的不是源代码,而是可以直接运行的二进制包。这就需要有相应的基础设施,负责对OpenJDK的构建,运行自动化测试,以及提供下载等。这些成本一直都是Oracle在承担。出于一些原因,Oracle不再提供对OpenJDK的LTS版本的安全更新支持。Oracle对OpenJDK的发布版本只提供6个月的支持,也就是到下一个版本发布为止。这6个月的支持包括两个按季度的安全更新。以 Java15 为例,只有最初发布的 15 版本,以及后续的两个安全更新版本 15.0.1 和 15.0.2。之后就不再有 15 版本的更新。如果要获取更新,只能升级到 Java 16。对于非LTS版本来说,这样还算合理。但是对LTS版本来说,不提供持续的安全更新是很大的问题。要获取更新,必须使用Oracle JDK。Oracle JDK 对个人用户是免费的,对商业用户是收费的。绝大多数人是不想掏钱的。因此,有很多的社区和公司就承担了提供LTS版本的更新的职责,就形成了非常多的Java发行版本。
火车发布模式的优缺点
优点:
新特性和改进:Java的版本更新频繁,每个版本都包含了大量的新特性和改进,这可以提高Java应用程序的性能、安全性和可靠性。
反馈和改进:Java的版本更新频繁,这可以让Java开发者和Java平台提供商更快地获取用户反馈,并进行改进和优化。
生态系统健康:Java的版本更新频繁,这可以保持Java生态系统的健康和活力,吸引更多的开发者和用户参与到Java生态系统中来。
技术更新:Java的版本更新频繁,这可以让Java开发者和Java平台提供商更快地掌握新的技术和趋势,保持技术的领先优势。
社区活跃:Java的版本更新频繁,这可以促进Java社区的活跃和发展,让Java开发者和用户更加紧密地联系在一起。
缺点:
版本更新频繁:Java的版本更新比较频繁,每年都会发布新的版本,这会给Java开发者带来一定的学习成本和适应压力。
兼容性问题:由于Java的版本更新比较频繁,不同版本之间可能存在兼容性问题,这会给Java开发者带来一定的麻烦。
集成测试难度大:Java的版本更新频繁,每个版本都包含了大量的新特性和改进,这会给Java开发者带来集成测试的难度,需要进行大量的测试和调试工作。
稳定性问题:Java的版本更新频繁,每个版本都可能存在一些新的bug,这会影响Java应用程序的稳定性和可靠性。
维护成本高:Java的版本更新频繁,每个版本都需要进行维护和支持,这会给Java开发者和Java平台提供商带来一定的成本压力。
总结:
在 JDK版本的选择上,如果是学习目的,那么无脑最新版。如果是生产需要,尽量选择长期维护的版本,不要使用最新版本的。因为新版本的 JDK,新功能没有经过生产环境的验证,如果想成为第一个吃螃蟹的人,一定要三思而后行。
Java 9
Jigsaw 模块系统
什么是模块系统?官方的定义是 A uniquely named, reusable group of related packages, as well as resources (such as images and XML files) and a module descriptor.
模块的载体是 jar 文件,一个模块就是一个 jar 文件,但相比于传统的 jar 文件,模块的根目录下多了一个 module-info.java 文件,即 module descriptor。 module descriptor 包含以下信息:
- 模块名称
- 依赖哪些模块
- 导出模块内的哪些包(允许直接 import 使用)
- 开放模块内的哪些包(允许通过 Java 反射访问)
- 提供哪些服务
- 依赖哪些服务
图-2:Java9 module
Jigsaw 模块系统使用示例:
项目A中 module-info.java:
// 模块A 对外暴露了包org.a.example.a1与org.a.example.a2路径的类, 其中org.a.example.a2包指定仅能模块B可用
module A {
exports org.a.example.a2; // 模块 A 对外暴露org.a.example.a2中的内容
exports org.a.example.a1 to B; // 模块 A 仅对模块 B 暴露org.a.example.a1中的内容
}
项目B中 module-info.java:
// 模块B依赖模块A,能使用 org.a.example.a1 与 org.a.example.a2 下的类
module B {
requires A;
}
项目C中 module-info.java:
// 模块C依赖模块A,其只能使用包org.a.example.a2下的类
module C {
requires A;
}
图-3 模块系统使用示例
模块系统的优点:
1、原生的依赖管理。有了模块系统,Java 可以根据 module descriptor 计算出各个模块间的依赖关系,具有了在编译期和执行期加以识别模块之间依赖项的能力。系统可以通过这些依赖项确保所有模块的子集合能满足程序的需求
2、精简 JRE 。引入模块系统之后,JDK 自身被划分为 94 个模块。通过 Java 9 新增的 jlink 工具,开发者可以根据实际应用场景随意组合这些模块,去除不需要的模块,生成自定义 JRE,从而有效缩小 JRE 大小。得益于此,JRE 11 的大小仅为 JRE 8 的 53%,从 218.4 MB缩减为 116.3 MB,JRE 中广为诟病的巨型 jar 文件 rt.jar 也被移除。更小的 JRE 意味着更少的内存占用,这让 Java 对嵌入式应用开发变得更友好。
图-4:Modular JDK
3、更好的兼容性。自打 Java 出生以来,就只有 4 种包可见性,这让 Java 对面向对象的三大特征之一封装的支持大打折扣。Java 9 之后,利用 module descriptor 中的 exports 关键词,模块维护者就精准控制哪些类可以对外开放使用,哪些类只能内部使用,换句话说就是不再依赖文档,而是由编译器来保证。
图-5:Java可访问级别
4、提升 Java 语言开发效率。Java 9 之后,JDK 被拆分为 94 个模块,每个模块有清晰的边界和独立的单元测试,对于每个 Java 语言的开发者而言,每个人只需要关注其所负责的模块,开发效率因此大幅提升。
String 优化
JDK9 之前的库的 String 类的实现使用了 char 数组来存放字符串,char 占用16位,即两字节。而在Java 9中,String类引入了一种称为"Compact Strings"的新实现方式,将字符串的表示方式从char数组改为byte数组,根据情况使用Latin1编码将Unicode字符映射到一个或两个字节的表示。这种实现方式可以大大减少内存使用,尤其是对于包含大量ASCII字符的字符串。
图-6:Java字符串结构变化
Latin1编码在ASCII字符集的基础上进行了扩展,它使用单字节编码方式,也就是说最多只能表示256个字母或符号,并且前128个和ASCII完全吻合。Latin1编码保存字符串示例如下:
图-7:Java9 不同编码格式下保存字符串 abcd 示例
Java9中对于符合 Latin1 编码的字符,会使用 byte 数组进行保存,并且标记编码格式coder为LATIN1。
Java9字符串保存逻辑源码如下:
/*
* Package private constructor. Trailing Void argument is there for
* disambiguating it against other (public) constructors.
*
* Stores the char[] value into a byte[] that each byte represents
* the8 low-order bits of the corresponding character, if the char[]
* contains only latin1 character. Or a byte[] that stores all
* characters in their byte sequences defined by the {@code StringUTF16}.
*/
String(char[] value, int off, int len, Void sig) {
if (len == 0) {
this.value = "".value;
this.coder = "".coder;
return;
}
if (COMPACT_STRINGS) { // 判断能否使用 Latin1 编码保存字符串
byte[] val = StringUTF16.compress(value, off, len);
if (val != null) {
this.value = val;
this.coder = LATIN1;
return;
}
}
this.coder = UTF16;
this.value = StringUTF16.toBytes(value, off, len);
}
JShell REPL
JShell 是 Java 9 新增的一个交互式的编程环境工具。它允许你无需使用类或者方法包装来执行 Java 语句。它与 Python 的解释器类似,可以直接输入表达式并查看其执行结果。
图-8 JShell REPL 使用示例
集合、Stream 和 Optional
-
集合
- 不可变集合 :
List.of()
、Set.of()
、Map.of()
、Map.ofEntries()
等方法
- 不可变集合 :
-
Stream
-
takeWhile:
default Stream takeWhile(Predicate
-