JDK17新特性梳理
一、为什么JDK17
-
生态强制升级:
业界长期 “你发任你发,我用 Java8”,但 Spring Framework 6.0+ 明确要求 Java 17+,Spring Boot 3.2+ 不仅要求 Java 17+,还需依赖 Spring Framework 6.1.1+,且仅支持特定构建工具版本:
Build Tool 最低版本要求 Maven 3.6.3 Gradle 7.5+(7.x 系列)、8.x 系列 -
版本定位关键:
- 是 JDK8 后首个生态成熟的 LTS 长期支持版本,比 JDK11 生态更完善;
- 建议跳过 JDK11 直接升级:JDK21 仅 “虚拟线程” 特性亮眼,其他特性对 JDK17 无显著优势。
二、语法层新特性
1.文本块
-
功能:解决多行字符串换行转义问题,用连续三个双引号包裹,支持
String.format
; -
新增转义符:
\
(置于行尾,连接上下两行)、\s
(表示单个空字符); -
示例:
String query = """ SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB` \s WHERE `CITY` = '%s' \ ORDER BY `EMP_ID`, `LAST_NAME`; """; System.out.println(String.format(query, "合肥")); // 输出无转义符的正确SQL
2.Switch 表达式增强
-
核心升级:支持 “语句” 与 “表达式” 双模式,新增多值匹配、
yield
关键字(替代 break 返回值); -
示例 1(多值匹配):
switch (name) {case "李白", "杜甫", "白居易" -> System.out.println("唐代诗人");case "苏轼", "辛弃疾" -> System.out.println("宋代诗人");default -> System.out.println("其他朝代诗人"); }
-
示例 2(作为表达式):
int tmp = switch (name) {case "李白", "杜甫", "白居易" -> 1;case "苏轼", "辛弃疾" -> 2;default -> {System.out.println("其他朝代诗人");yield 3; // 返回默认值} };
3.instanceof 的模式匹配
-
功能:判断变量类型后,无需手动强转,直接在分支中使用匹配类型的变量;
-
示例:
if (o instanceof Integer i && i > 0) {System.out.println(i.intValue()); // 直接用i,无需强转 } else if (o instanceof String s && s.startsWith("t")) {System.out.println(s.charAt(0)); // 直接用s }
4.var 局部变量推导
-
功能:对类型可直接推导的局部变量,用
var
声明,简化代码; -
示例:
var nums = new int[] {1, 2, 3, 4, 5}; // 推导为int[] var sum = Arrays.stream(nums).sum(); // 推导为int
-
注意:需权衡 “简化代码” 与 “Java 强类型安全性”,仁者见仁。
三、模块化及类封装
1.记录类(record)
- 定位:不可变数据结构,替代 BO/VO/DTO 等仅用于值传递的复杂对象;
- 特性:
- JDK14 引入,JDK16 正式转正;
- 声明时直接定义属性(如
public record Point(int x, int y) {}
),初始化后属性不可修改(反射也不行); - 自动生成
toString()
、hashCode()
、equals()
且为final
,不可定制; - 属性获取方法为 “属性名 ()”(如
p.x()
),而非传统getX()
。
2.隐藏类(Hidden Classes)
- 引入版本:JDK15;
- 核心逻辑:不依赖类加载器,通过读取目标类字节码创建 “对其他类隐藏的 Class 对象”,再通过反射调用;
- 价值:提升 Java 动态语言能力,替代 Spring 等框架中繁琐低效的 ASM 字节码操作,成为动态类生成的新标准。
// 步骤1:同样用ASM生成代理类的字节码(和JDK8一样,字节码生成逻辑不变)
byte[] proxyBytes = ...; // 动态生成的代理类字节码// 步骤2:直接通过MethodHandles生成隐藏类(无需关心类加载器)
MethodHandles.Lookup lookup = MethodHandles.lookup();
// 关键API:defineHiddenClass(字节码数组 + 是否初始化 + 嵌套类选项)
Class<?> hiddenProxyClass = lookup.defineHiddenClass(proxyBytes, true, MethodHandles.Lookup.ClassOption.NESTMATE).lookupClass();// 步骤3:通过MethodHandle调用方法(无法直接引用类型,只能间接调用)
// 3.1 获取构造方法,创建实例
Constructor<?> constructor = hiddenProxyClass.getConstructor();
Object proxyInstance = constructor.newInstance();// 3.2 获取sayHello方法,调用
Method sayHelloMethod = hiddenProxyClass.getMethod("sayHello", String.class);
String result = (String) sayHelloMethod.invoke(proxyInstance, "张三");
System.out.println(result);// 优势:
// 1. 代理类对其他类不可见(不能写 UserServiceProxy proxy = ...);
// 2. hiddenProxyClass无人引用后,会被GC回收,无内存泄漏;
// 3. 无需手动实现类加载器,API更简洁,不易出错。
3.密封类(Sealed Classes)
- 转正版本:JDK17;
- 功能:限制父类的子类范围,防止随意继承打破内置行为(如 JDK8 类加载双亲委派可被随意修改的安全问题);
- 使用规则:
- 父类用
sealed
修饰,需通过permits
指定允许的子类(如public sealed abstract class Shape permits Circle, Rectangle, Square {}
); - 子类需声明密封属性:
final
(不可继承)、non-sealed
(可随意继承)、sealed
(继续用permits
指定子类)(public non-sealed class Square extends Shape
);
- 父类用
- 限制:父类与子类必须在同一显式命名的 module 下,且子类需直接继承父类。
4.模块化系统(Module System)
-
定义:JDK9 引入,是
package
上层的抽象,聚合相关包与资源,通过module-info.java
描述,JDK17 中以.jmod
文件替代传统.jar
文件; -
核心操作:
- 声明模块:在模块根目录创建
module-info.java
,用module
关键字声明(如module roy.demomodule {}
),模块名需全局唯一(惯例小写,用.
连接); - 声明依赖(require):
- 依赖外部模块:如依赖 JUnit 需
requires junit;
(非模块化 Jar 包默认模块名为 “去掉版本的 Jar 名”); - 依赖 JDK 内置模块:如用 JDBC 需
requires java.sql;
; - 编译时依赖:
requires static
(仅编译需该模块,运行时可选,类似 Maven 的 compile scope);
- 依赖外部模块:如依赖 JUnit 需
- 暴露 API(exports/opens):
exports 包名
:对外开放指定包,编译和运行时可访问,但不支持反射;opens 包名
:对外开放指定包,支持反射访问(解决跨模块反射调用报错问题);
- 服务机制(uses/provides):
- 服务提供方(如 demoModule2):用
provides 接口名 with 实现类1, 实现类2;
暴露服务(需先exports
接口所在包); - 服务调用方(如 demoModule):用
uses 接口名;
声明使用服务,通过ServiceLoader.load(接口名)
调用;
- 服务提供方(如 demoModule2):用
- 构建模块化 Jar 包:
- 执行命令:
java --module-path 模块路径 -m 模块名/主类全路径
(如java --module-path demoModule.jar:demoModule2.jar -m roy.demomodule/com.roy.spi.ServiceDemo
); - 查看模块:
java --module-path 模块路径 --list-modules
;
- 执行命令:
- 声明模块:在模块根目录创建
-
类加载机制调整:
JDK8 类加载体系 JDK9+(含 JDK17)类加载体系 核心调整点 BootstrapClassLoader(根加载器) BootstrapClassLoader 1. 替换扩展加载器:用 PlatformClassLoader 替代 ExtClassLoader,因模块化天生支持扩展,无需 ExtClassLoader;2. 调整父类:PlatformClassLoader 与 AppClassLoader 继承自 BuildinClassLoader(实现模块化类加载逻辑);3. 双亲委派优化:Platform/AppClassLoader 加载类前,先判断类所属系统模块,优先委派给对应模块的加载器 ExtClassLoader(扩展加载器) PlatformClassLoader(平台类加载器) AppClassLoader(应用类加载器) AppClassLoader 自定义类加载器 自定义类加载器
四、GC 调整
- ZGC 转正:
- 引入:JDK11 预览,JDK15 正式投入使用,JDK17 中相关参数已稳定;
- 启用:
-XX:+UseZGC
; - 补充:支持 RedHat 的 Shenandoah 垃圾回收器,启用参数
XX:+UseShenandoahGC
(Oracle 以可选方案集成)。
- 废除 CMS:
- 时间:JDK14 彻底删除;
- 原因:G1 已完善,ZGC/Shenandoah 等现代 GC 更高效,CMS 实现复杂;
- 同步删除:Serial 垃圾回收器、SerialOld(原作为 CMS 补充方案)。
五、GraalVM 虚拟机
- 背景:
- 定位:替代 HotSpot 的潜力虚拟机,用 Java 编写(对比 C/C++ 编写的 C1/C2 编译器,更易维护);
- 核心能力:支持 JIT(即时编译) 与 AOT(提前编译),解决 Java 微服务场景下 “启动慢、预热久” 的痛点,适配云原生;
- 生态支持:Spring Boot、Micronaut 等微服务框架,Oracle Cloud、AWS 等云平台均支持。
- 使用步骤:
- 下载:官网(https://www.graalvm.org)下载对应版本(当前支持 Java17/21);
- 配置:解压后配置
JAVA_HOME
,验证java -version
(显示 “Oracle GraalVM”); - 基础使用:与普通 JDK 一致(
javac 编译
、java 执行
); - AOT 编译(本地镜像):
- 命令:
native-image 类名
(需先安装 zlib-devel 依赖,解决 “找不到 -lz” 错误); - 优势:本地镜像无需 JVM 即可运行,启动速度极快(示例 Hello 程序执行时间
real 0m0.006s
,远快于普通 JDK 的0.059s
)。
- 命令:
关键问题及答案
问题 1:为什么说 JDK17 是 JDK8 时代程序员的 “必选项”,而非 “可选项”?其生态支持的核心依据是什么?
答案:JDK17 成为 “必选项” 的核心原因的两点:
- 生态强制绑定:作为 Java 应用基石的 Spring Framework 6.0+ 明确要求 “Java 17+”,Spring Boot 3.2+ 不仅要求 “Java 17+”,还需依赖 Spring Framework 6.1.1+,且仅支持 Maven 3.6.3+、Gradle 7.5 + 等工具,若不升级 JDK17,无法使用主流框架的新版本特性;
- 版本性价比最优:JDK17 是 JDK8 后首个生态成熟的 LTS 版本,相比 JDK11 生态更完善;而 JDK21 仅 “虚拟线程” 特性亮眼,其他特性对开发效率提升有限,跳过 JDK11 直接升级 JDK17 更实惠,避免多次升级成本。
问题 2:JDK17 中 “record 记录类” 与传统 POJO 相比,核心差异是什么?在实际开发中适合解决什么问题?
答案:核心差异及适用场景如下:
对比维度 | record 记录类 | 传统 POJO |
---|---|---|
不可变性 | 属性默认private final ,初始化后不可修改(反射也不行) |
需手动添加private final ,否则属性可修改 |
方法生成 | 自动生成toString() 、hashCode() 、equals() ,且方法为final (不可重写) |
需手动编写或依赖 Lombok 生成,方法可重写 |
属性访问 | 用 “属性名 ()” 获取(如p.x() ) |
用getX() 获取(需遵循 JavaBean 规范) |
适用场景 | 仅用于值传递的场景(如 BO/VO/DTO),无需定制业务逻辑 | 需包含业务逻辑(如属性修改、复杂方法)的对象 |
实际开发中,record 记录类可大幅简化 “仅承载数据、无业务逻辑” 的类定义,减少模板代码(如无需写 getter/toString 等),提升开发效率。 |
问题 3:JDK17 模块化系统(Module System)相比 JDK8 的 “包机制”,最大的改进是什么?对大型项目开发有何实际价值?
答案:最大改进是 从 “包级别的代码组织” 升级为 “模块级别的资源隔离与依赖管理”,JDK8 的包机制仅能通过访问修饰符(public/private)控制类访问范围,无法限制跨包 / 跨 Jar 的依赖;而模块化系统通过module-info.java
实现:
-
精确依赖管理:用
require
明确声明依赖的模块,避免 “隐式依赖” 导致的版本冲突(如依赖 JDK 内置功能需显式requires java.sql
); -
资源隔离:用
exports/opens
仅暴露必要的 API,隐藏内部实现(如仅对外开放com.roy.service
包,内部com.roy.util
包不暴露),降低耦合; -
定制轻量 JRE:基于
.jmod
文件,通过jlink -p $JAVA_HOME/jmods --add-modules 所需模块 --output 自定义JRE
生成仅含项目所需模块的 JRE(如仅用java.base
模块),减少部署体积,适配云原生轻量化需求;对大型项目而言,这些改进可降低跨团队协作的依赖冲突风险,简化多模块项目的管理,同时减少线上部署资源占用。