从 1995 年 JDK Beta 发布至今,Java 已迭代了 18 个大版本。其中 Java 8、11、17 为长期支持(LTS,Long-term support)的版本。 根据 JRebel 2022 年提供的报告,大部分人还在使用 Java 8,其次是 Java 11,但随着 Java 17(2021.09) 的发布,Java 将迎来新的格局。
Java 现状
jdk 使用情况
Java 8 位居榜首,其次是 Java 11,Java 7 或更早版本只占了 5%。
jdk17 升级趋势
综合来看,62% 的人打算在未来 12 个月内迁移到 JDK 17。8% 的人则表示不会升级。
Java 9
接口中支持 private 方法
1 | public interface InterfacePrivate { |
各个版本的 JDK 接口对比
Java 7 及以前 | Java 8 | Java 9 | |
---|---|---|---|
常量 | ✅ | ✅ | ✅ |
抽象方法 | ✅ | ✅ | ✅ |
默认方法 | ✅ | ✅ | |
静态方法 | ✅ | ✅ | |
私有方法 | ✅ | ||
私有静态方法 | ✅ |
模块化
JDK 9 引入了一个新的特性叫做 JPMS(Java Platform Module System),项目代号为 Jigsaw。
背景
我们知道在 Java 9 之前代码都是按包进行组织的,而模块则是在包的概念上又增加了一个更高层的抽象,它将多个逻辑、功能上相关的包及资源文件(如 xml 文件)组织成一个可重用的逻辑单元,这个逻辑单元就称为模块。听起来是不是和 gradle 或者 maven 中的模块很像?
通常情况下模块中包含着一个模块描述符文件(module-info.class),用来指定模块的名字、依赖(每个模块必须显式声明依赖的模块)、对外暴露的包(其余的包则对外不可见)、模块提供的服务、模块使用的服务以及允许哪些模块可以对其进行反射等配置信息。模块最终都会打包成 jar 包来分发和使用,那么模块打包的 jar 包和普通的 jar 包有什么区别呢?模块和普通的 jar 包几乎是一样的,只不过比普通的 jar 包在根目录下多了一个模块描述符文件(module-info.class)而已。
模块化的目标
根据 JSR 376 规范的描述,模块化要实现以下几个目标:
- 可靠的配置
- 模块应该提供一种机制来 显式的声明模块间的依赖,从而可以寻着依赖路径提取出所有模块的一个子集来支撑系统的运行。
- 强封装
- 模块化机制要求模块中的包(package)只有在 显式的导出 后才可以被其他模块使用,并且其他模块必须 显式的声明 它需要这个模块的包之后才能使用这些包。这种机制可以提高安全性,攻击者能够访问的类越少越安全。
- 此外,模块的强封装也有助于我们思考如何组织代码,获得更合理的设计。
- 增强可扩展性和可维护性
- 之前 Java 平台是作为一个整体部署到操作系统之上的,其中包含了不计其数的包和类,无论我们是否使用,它们都在那里。如果某些类需要更新或打补丁,则要替换整个 Java 平台,难以维护和扩展。
- 可定制的运行环境
- Java 9 将平台划分成了 95 个模块,通过使用模块化技术,我们可以定制运行环境,只包含硬件和应用需要的包和类即可。
- 例如,设备不支持图形界面的话,我们就可以创建一个不包含 GUI 模块的运行环境,这将极大的降低运行环境的大小,节省不少空间。
JDK 8 和 JDK 17 的 jar 包对比
模块的类型
Java 9 的模块可以分为 3 种类型:
- 命名模块(Named Module)
- 命名模块也称应用模块(Application Module),模块的根目录中存在声明文件 module-info.java。
- 无名模块(Unnamed Module)
- 无名模块是在没有声明文件 module-info.java 的情况下构建的 jar。这意味着在 Java 8 和更早版本构建的所有 jar 都是无名模块。无名模块可以读取到其他所有的模块,并且也会将自身所有的包暴露出去,这为当前应用程序使用 Java 9 提供了极大的便利。
- 虽然无名模块导出了自身所有的包,但这并不意味着命名模块可以读取无名模块,因为命名模块在 module-info.java 中无法声明对无名模块的依赖,无名模块导出所有包的目的在于让其他无名模块可以加载这些类。
- 自动模块(Automatic Module)
- 如果命名模块想要读取没有 module-info.java 的 jar 怎么办?自动模块就是为了解决这个问题,你唯一需要做的就是将 jar 文件放在模块路径而不是类路径中。一个没有 module-info.java 的 jar 一旦放入模块路径,它就会自动成为一个自动模块。
模块声明
安装完 JDK 9 之后可以通过 java --list-modules
命令来列出所有的模块,这些模块都位于 $JAVA_HOME/jmods
目录下。
jdk 17 的模块,@ 后面是版本号
模块指令
java.naming 模块的声明文件
- requires 指令。
- 该指令用于指定当前模块的依赖模块,每个模块必须显式的声明依赖模块。
- exports 和 exports to 指令。
- exports 指令用于指定当前模块的导出包,而 exports to 指令则限定哪些模块可以访问该导出包。
- uses 指令。
- 该指令用于说明当前模块所使用的服务,使当前模块成为服务的消费者。
- provides with 指令。
- 该指令用于说明当前模块提供了某个服务的实现,使当前模块成为服务的提供者。
- …
Java 11
支持类型推断
- 局部变量支持类型推断,字段不支持类型推断。
- 类型推断特别适合 for 循环。
1 | /** |
Java 14
switch 增强
switch 中可以使用 ->
,这样就不用在每个分支后面增加 break。
1 | /** |
switch 一直以来都只是一个语句,不会生成结果。现在使用新的关键字 yield 可以从 switch 中返回结果。
1 | /** |
Java 15
文本块
jdk 15 中添加了文本块(text block),这是从 python 借鉴而来的一个新特性。但 """你好"""
这种不换行的写法是不支持的。
1 | /** |
为了支持文本块,String 类里添加了一个新的 formatted() 方法。formatted() 是一个成员方法,是为了仿照 String.format() 这个静态方法而设计的。
1 | /** |
更详细的 NullPointException 信息
1 | /** |
执行结果
Java 16
增加 record 关键字
要让一个类的对象可以用作 Map(或 Set )中的键,需要重写 equals() 和 hashCode() 方法。写对很难,如果以后要修改这个类的话,还很容易破坏它们。
1 | /** |
instanceof 增强
一旦确定类型后,就永远不需要对其转型了。
1 | /** |
Java 17
密封类
1 | /** |
1 | /** |
密封接口
1 | /** |
小结
细细想来,连 JDK 8(2014.3)的发布都已经是 8 年前的事情,更别说 JDK 5(2004.9),是时候拥抱变化,迎接 JDK 17 了。