作为一个写了几年 Java 的程序员,每次 JDK 出新版本我都会格外关注。尤其是 JDK17 这种长期支持版本(LTS),就像手机系统里的稳定版,会被许多公司用好几年。今天就用大白话聊聊 JDK17 里那些实用的新特性,不管是刚入行的新手还是老程序员,说不定都能用上。
写代码更顺手了,这两个语法糖要试试
密封类:终于能管住那些乱继承的类了
以前写代码时,定义一个抽象类总怕别人乱继承。列如我定义了个Shape类,本意是只让Circle、Rectangle这几个类继承,结果总有同事为了图方便,随意写个类就去继承,到最后代码乱得没法维护。
JDK17 的密封类(Sealed Classes)就是来解决这个问题的。用sealed关键字声明的类,能明确指定哪些子类可以继承它,其他类想继承都不行。
// 只允许Circle、Rectangle、Triangle继承
public sealed class Shape
permits Circle, Rectangle, Triangle {
public abstract double area();
}
// 子类必须声明成final(不能再被继承)
public final class Circle extends Shape {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
这样一来,类的继承关系就清清楚楚,别人想乱改都难。编译器还会帮你检查,要是子类不在permits列表里,直接就报错,从源头避免了混乱。
switch 表达式升级,不用再写一堆 if-else 了
以前处理不同类型的数据时,总得写一串if (obj instanceof …),不仅啰嗦,还容易出错。JDK17 里的 switch 表达式支持了类型匹配,代码一下子清爽了不少。
列如要格式化不同类型的数据,以前得这么写:
public String format(Object obj) {
if (obj instanceof Integer i) {
return “数字:” + i;
} else if (obj instanceof String s) {
return “字符串:” + s;
} else {
return “不知道啥类型”;
}
}
目前用 switch 改写,是不是简洁多了:
public String formatWithSwitch(Object obj) {
return switch (obj) {
case Integer i -> “数字:” + i;
case String s -> “字符串:” + s;
case null -> “啥都没有”;
default -> “不知道啥类型”;
};
}
最贴心的是还能直接处理 null 值,以前写 switch 时要是传个 null 进去,肯定会报空指针异常,目前专门加了case null,再也不用提前判断了。
程序跑得更快了,这些优化藏在细节里
ZGC 垃圾回收器:大内存应用的救星
以前做一个电商项目,服务器内存用到 32G 以上时,垃圾回收常常卡顿,用户下单时偶尔会遇到几秒的延迟,投诉不少。后来换成 JDK17 的 ZGC,情况好了许多。
ZGC 是个低延迟的垃圾回收器,在 JDK17 里又做了升级:不管堆内存是 8MB 还是 16TB,回收时的停顿时间都能控制在毫秒以内。我们把服务器的堆内存调到 64G 后,用 ZGC 跑了一个月,高峰期的响应时间标准差比以前降了快一半,用户几乎感觉不到卡顿。
启用也很简单,启动时加个参数就行:-XX:+UseZGC。如果是新项目,强烈提议试试;老项目升级时,这个参数能解决不少性能问题。
矢量 API:让 CPU 全力干活
这个特性可能平时用得不多,但做数据分析、图像处理的同学会很喜爱。简单说,就是让 CPU 一次能处理多个数据,列如同时给一个数组的所有元素做加法,速度能比普通循环快好几倍。
列如计算一个 float 数组的总和,用矢量 API 来写:
public static float sum(float[] array) {
// 选一个合适的矢量规格
int species = FloatVector.SPECIES_PREFERRED;
int length = array.length;
int i = 0;
// 初始化一个求和的矢量
FloatVector sumVec = FloatVector.zero(species);
// 批量处理数组元素
for (; i <= length – species.length(); i += species.length()) {
FloatVector vec = FloatVector.fromArray(species, array, i);
sumVec = sumVec.add(vec);
}
// 处理剩下的元素
float sum = sumVec.reduceLanes(VectorOperators.ADD);
for (; i < length; i++) {
sum += array[i];
}
return sum;
}
我们测试时用了一个 100 万元素的数组,这段代码比普通 for 循环快了 3 倍多。不用写复杂的 native 代码,纯 Java 就能利用 CPU 的高级特性,这波优化很实在。
更安全了,但这些老代码得改改
内部 API 不让随意用了
以前有些程序员喜爱用sun.misc这类内部 API,觉得方便。但这些 API 不是公开标准,说不定哪个版本就变了。JDK17 里把这些 API 默认封起来了,想访问就得专门加参数,而且还会警告。
列如以前用sun.misc.Unsafe的地方,目前最好换成java.util.concurrent里的类。我去年升级项目时,用jdeps –jdk-internals命令查了一下,发现有十几个地方用到了内部 API,花了一周时间才改完。虽然麻烦,但长远来看,能避免后来升级时出大问题。
Security Manager 被移除了
这个东西从 JDK1.0 就有了,主要用来做安全控制,但目前几乎没人用了。JDK17 干脆把它移除了,要是老项目里还有相关代码,得赶紧换成别的方案,列如用模块权限控制,或者在服务器层面做隔离。
还有这些小改善,用起来挺舒服
启动更快了:JDK17 优化了类加载机制,大型应用的启动时间能缩短 15% 左右。我们公司的一个 Spring Boot 项目,以前启动要 30 多秒,升级后不到 26 秒就起来了。
随机数生成更方便:新增了RandomGenerator接口,支持好几种随机算法,生成随机数更灵活,还能保证线程安全。
Applet API 被废弃了:这个估计没人用了,毕竟目前浏览器都不支持 Java 插件了,删了也清净。
最后说点升级的小提议
如果打算把项目升到 JDK17,提议先做这几件事:
用jdeps工具检查一下有没有依赖内部 API,早发现早修改。
升级依赖的框架,列如 Spring Boot 至少要 2.6 以上,Hibernate 得 5.6 以上才行。
试试 ZGC 垃圾回收器,特别是内存大的应用,性能提升很明显。
旧代码里的if-else类型判断,慢慢换成新的 switch 表达式,代码会更清爽。
JDK17 作为长期支持版本,会维护到 2029 年,值得花时间升级。刚开始可能会有点小麻烦,但用熟了就会发现,这些新特性是真的能提高开发效率,还能让程序跑得更快更稳。
你们项目升级 JDK17 了吗?遇到了哪些问题?来评论区聊聊~



