Java9至Java17版本新特性

2023年 10月 14日 76.3k 0

大兄弟们,周五了,赶紧看篇文章摸会鱼,帮忙对文章提提意见

本文依赖 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 版本新特性。

image.png

图-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 反射访问)
    • 提供哪些服务
    • 依赖哪些服务

    image.png

    图-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;
        }
    

    image.png

    图-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 对嵌入式应用开发变得更友好。

    image.png

    图-4:Modular JDK

    3、更好的兼容性。自打 Java 出生以来,就只有 4 种包可见性,这让 Java 对面向对象的三大特征之一封装的支持大打折扣。Java 9 之后,利用 module descriptor 中的 exports 关键词,模块维护者就精准控制哪些类可以对外开放使用,哪些类只能内部使用,换句话说就是不再依赖文档,而是由编译器来保证。

    image.png

    图-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字符的字符串。

    image.png

    图-6:Java字符串结构变化

    Latin1编码在ASCII字符集的基础上进行了扩展,它使用单字节编码方式,也就是说最多只能表示256个字母或符号,并且前128个和ASCII完全吻合。Latin1编码保存字符串示例如下:

    image.png

    图-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 的解释器类似,可以直接输入表达式并查看其执行结果。

    image.png

    图-8 JShell REPL 使用示例

    集合、Stream 和 Optional

    • 集合

      • 不可变集合 :List.of()Set.of()Map.of()Map.ofEntries() 等方法
    • Stream

      • takeWhile:default Stream takeWhile(Predicate

    相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论