JDK新特性综合实战:JDK17-21最值得用的10个特性
2026/4/30大约 8 分钟
JDK新特性综合实战:JDK17-21最值得用的10个特性
适读人群:正在评估是否升级到JDK17/21的Java团队、想快速了解近几年Java演进的工程师 | 阅读时长:约20分钟 | 文章类型:综合实战+升级决策指南
开篇故事
我们团队在2023年初把主要服务从JDK8升到了JDK17,在2023年底部分服务进一步升到了JDK21。
升级前,我把JDK9到JDK21所有的特性梳理了一遍,筛出了真正在日常开发里有价值的。有些特性看起来很炫,但业务代码基本用不到;有些特性看起来小,但每天会用到几十次。
今天分享这个筛选结果:10个真正值得迁移的特性,以及一个可直接用的升级决策框架。
一、10个最值得用的特性
特性1:文本块(Text Blocks,JDK15 GA)
// 旧写法:JSON字符串拼接是噩梦
String json = "{" +
"\"name\": \"张三\"," +
"\"age\": 28," +
"\"address\": {" +
" \"city\": \"北京\"" +
"}" +
"}";
// 新写法:文本块,再也不用转义引号
String json = """
{
"name": "张三",
"age": 28,
"address": {
"city": "北京"
}
}
""";适用场景:内嵌SQL、JSON、HTML、XML字符串。每个项目都会用到。
特性2:Record类型(JDK16 GA)
// 旧写法:20行POJO
public class UserDTO { /* 构造器/getter/equals/hashCode/toString */ }
// 新写法:1行搞定
record UserDTO(Long id, String name, String email) {}适用场景:DTO、值对象、多值返回、Stream中间结果。
特性3:instanceof模式匹配(JDK16 GA)
// 旧写法
if (obj instanceof String) {
String s = (String) obj;
return s.toUpperCase();
}
// 新写法
if (obj instanceof String s) {
return s.toUpperCase(); // s直接可用
}特性4:Sealed Classes(JDK17 GA)
// 精确控制继承,编译器帮你检查完整性
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double w, double h) implements Shape {}
record Triangle(double a, double b, double c) implements Shape {}特性5:switch表达式(JDK14 GA)
// 旧switch(语句,容易漏break)
String result;
switch (day) {
case MONDAY: result = "工作日"; break;
case SATURDAY:
case SUNDAY: result = "周末"; break;
default: result = "工作日";
}
// 新switch(表达式,简洁安全)
String result = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "工作日";
case SATURDAY, SUNDAY -> "周末";
};特性6:虚拟线程(JDK21 GA)
// 适合IO密集型服务,一行配置(Spring Boot 3.2+)
// spring.threads.virtual.enabled=true
// 或者手动创建
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();特性7:switch的模式匹配(JDK21 GA)
// 配合sealed interface,替代if-else instanceof链
String describe(Shape shape) {
return switch (shape) {
case Circle c -> "圆,半径=" + c.radius();
case Rectangle r -> "矩形," + r.w() + "x" + r.h();
case Triangle t -> "三角形";
};
}特性8:var类型推断(JDK10 GA)
// 适合局部变量,减少冗余类型声明
var list = new ArrayList<String>(); // 不用写 ArrayList<String>
var map = new HashMap<String, List<Integer>>();
// 注意:只在局部变量里用,方法参数和返回值不用var特性9:SequencedCollection(JDK21 GA)
// 统一API:任何有序集合都可以用getFirst/getLast
List<String> list = List.of("a", "b", "c");
System.out.println(list.getFirst()); // a
System.out.println(list.getLast()); // c特性10:NullPointerException的改进消息(JDK14+,默认启用于JDK15)
// 旧NPE:NullPointerException(什么信息都没有)
// 新NPE:Cannot invoke "String.length()" because "user.address" is null
// 精确告诉你哪个引用是null,节省调试时间二、升级决策框架
三、完整代码实现
代码一:10个特性的综合实战示例
package com.laozhang.jdk21;
import java.util.*;
import java.util.stream.Collectors;
/**
* JDK17-21特性综合实战
* 用一个"订单处理"场景把所有特性串起来
*/
public class ModernJavaShowcase {
// 特性4:sealed interface + Record(精确建模订单状态)
sealed interface OrderEvent permits
OrderPlaced, OrderPaid, OrderShipped, OrderCompleted, OrderRefunded {}
record OrderPlaced(String orderId, String userId, long amountCents) implements OrderEvent {}
record OrderPaid(String orderId, String paymentId) implements OrderEvent {}
record OrderShipped(String orderId, String trackingNo) implements OrderEvent {}
record OrderCompleted(String orderId) implements OrderEvent {}
record OrderRefunded(String orderId, long refundCents, String reason) implements OrderEvent {}
// 特性2:Record作为DTO
record OrderSummary(String orderId, String status, String detail) {}
// 特性7:switch模式匹配处理事件
static OrderSummary processEvent(OrderEvent event) {
return switch (event) {
// 特性9:when守卫
case OrderPlaced(var id, var uid, long amt) when amt > 100000 ->
new OrderSummary(id, "PLACED_VIP",
String.format("VIP用户 %s 下单 %.2f 元,需人工审核", uid, amt / 100.0));
case OrderPlaced(var id, var uid, var amt) ->
new OrderSummary(id, "PLACED",
String.format("用户 %s 下单 %.2f 元", uid, amt / 100.0));
case OrderPaid(var id, var payId) ->
new OrderSummary(id, "PAID", "支付ID: " + payId);
case OrderShipped(var id, var tracking) ->
new OrderSummary(id, "SHIPPED", "运单: " + tracking);
case OrderCompleted(var id) ->
new OrderSummary(id, "COMPLETED", "订单完成");
case OrderRefunded(var id, long refund, var reason) ->
new OrderSummary(id, "REFUNDED",
String.format("退款 %.2f 元,原因: %s", refund / 100.0, reason));
};
}
// 特性1:文本块生成报告
static String generateReport(List<OrderSummary> summaries) {
var rows = summaries.stream()
.map(s -> String.format(" | %-12s | %-12s | %s",
s.orderId(), s.status(), s.detail()))
.collect(Collectors.joining("\n"));
return """
========================================
订单处理报告
========================================
%s
========================================
共处理 %d 条事件
""".formatted(rows, summaries.size());
}
public static void main(String[] args) {
// 特性8:var类型推断
var events = List.of(
new OrderPlaced("ORD001", "USR001", 5000L),
new OrderPlaced("ORD002", "USR002", 200000L), // VIP
new OrderPaid("ORD001", "PAY-XYZ"),
new OrderShipped("ORD001", "SF-123456"),
new OrderCompleted("ORD001"),
new OrderRefunded("ORD002", 200000L, "商品缺货")
);
var summaries = events.stream()
.map(ModernJavaShowcase::processEvent)
.collect(Collectors.toList());
System.out.println(generateReport(summaries));
System.out.println("=== 特性3:instanceof模式匹配 ===");
Object obj = "Hello, JDK21";
// 特性3
if (obj instanceof String s && s.startsWith("Hello")) {
System.out.println("问候语长度: " + s.length());
}
System.out.println("\n=== 特性10:NPE改进消息 ===");
System.out.println("JDK15+的NPE会精确告诉你哪个引用是null");
System.out.println("例如:Cannot invoke \"String.length()\" because \"name\" is null");
System.out.println("\n=== 特性9:SequencedCollection ===");
var orderIds = new LinkedHashSet<String>();
orderIds.add("ORD001");
orderIds.add("ORD002");
orderIds.add("ORD003");
System.out.println("最早的订单: " + orderIds.getFirst());
System.out.println("最新的订单: " + orderIds.getLast());
}
}代码二:升级评估工具类
package com.laozhang.jdk21;
/**
* JDK升级自检清单
* 在升级前跑一遍,了解你的项目用了哪些可能有兼容性问题的特性
*/
public class UpgradeChecklist {
public static void main(String[] args) {
System.out.println("=== JDK17/21升级自检清单 ===\n");
System.out.println("【必须处理的兼容性问题】");
System.out.println("□ Unsafe API:sun.misc.Unsafe的部分方法在JDK17+不可用");
System.out.println("□ 反射访问限制:JDK9+的模块系统限制了非public反射");
System.out.println("□ --illegal-access参数:JDK17中完全移除");
System.out.println("□ Java EE模块:javax.*包在JDK11被移除,改用jakarta.*");
System.out.println();
System.out.println("【框架版本要求】");
System.out.println("□ Spring Boot:3.x需要JDK17+,2.7.x支持JDK17但不支持JDK21");
System.out.println("□ MyBatis:3.5.11+完整支持JDK17");
System.out.println("□ Jackson:2.13+支持Record,2.15支持JDK21新类型");
System.out.println("□ Lombok:1.18.24+支持JDK17,1.18.30+支持JDK21");
System.out.println();
System.out.println("【升级获得的能力】");
System.out.println("✓ JDK11:var推断、新String方法、HttpClient");
System.out.println("✓ JDK14:NPE精确消息");
System.out.println("✓ JDK15:文本块正式GA");
System.out.println("✓ JDK16:Record、instanceof模式匹配");
System.out.println("✓ JDK17:Sealed Classes、更好的随机数API [LTS]");
System.out.println("✓ JDK21:虚拟线程、switch模式匹配、SequencedCollection [LTS]");
System.out.println();
System.out.println("【推荐升级路径】");
System.out.println("JDK8 → JDK17(先稳下来)→ JDK21(IO密集服务优先)");
System.out.println("\n=== 当前运行环境 ===");
System.out.println("JDK版本: " + System.getProperty("java.version"));
System.out.println("JVM名称: " + System.getProperty("java.vm.name"));
// 检查是否支持虚拟线程
try {
Thread.ofVirtual().unstarted(() -> {});
System.out.println("虚拟线程支持: 是(JDK21+)");
} catch (UnsupportedOperationException e) {
System.out.println("虚拟线程支持: 否(需要JDK21+)");
}
// 检查SequencedCollection
try {
Class.forName("java.util.SequencedCollection");
System.out.println("SequencedCollection: 可用(JDK21+)");
} catch (ClassNotFoundException e) {
System.out.println("SequencedCollection: 不可用(需要JDK21+)");
}
}
}四、踩坑实录
坑1:升到JDK17,Lombok失效导致大量编译错误
报错现象:
error: variable declaration not allowed here
annotations are not supported in module declarations根本原因:
Lombok老版本(1.18.22以前)和JDK17有兼容性问题。
具体解法:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version> <!-- 支持JDK21 -->
</dependency>坑2:升到JDK17,Spring Boot 2.x的某些自动配置失效
报错现象:
InaccessibleObjectException: Unable to make field accessible
--add-opens java.base/java.util=ALL-UNNAMED根本原因:
JDK9+的模块系统限制了通过反射访问模块内部类。Spring Boot 2.x的部分功能依赖反射访问,JDK17强制执行了这些限制。
具体解法:
选一:
- 升级到Spring Boot 3.x(推荐,长期解法)
- 在JVM启动参数里加
--add-opens(临时方案):
java --add-opens java.base/java.util=ALL-UNNAMED \
--add-opens java.base/java.lang=ALL-UNNAMED \
-jar app.jar坑3:text block里的缩进问题
触发代码:
// 错误:缩进被包含到字符串里了
if (condition) {
String sql = """
SELECT * ← 这个缩进会成为字符串的一部分
FROM user
WHERE id = 1
""";
}根本原因:
text block的结束符"""决定了缩进的基准。如果结束符缩进更多,额外的空格会被保留。
具体解法:
把结束符"""和内容对齐,或者使用String.stripIndent()。推荐把结束符放在一行:
String sql = """
SELECT *
FROM user
WHERE id = 1
"""; // 结束符与内容对齐,自动去除公共缩进五、总结与延伸
JDK17到21这5年,Java的语言表达力有了明显的提升。文本块、Record、模式匹配加在一起,让Java代码的噪音(样板代码)减少了很多,更能专注于业务逻辑。
虚拟线程是这个时期最重要的性能特性,配合结构化并发,让Java在IO密集型场景重新有了竞争力。
最推荐的升级路径:
- 所有项目:JDK8/11 → JDK17(稳定的LTS,改动最小)
- IO密集型服务:JDK17 → JDK21(虚拟线程是核心驱动力)
- 新项目:直接JDK21
不用等完美时机,升级JDK的ROI很高——代码更简洁,性能更好,安全补丁更新,框架支持更好。
