JDK9-21新特性速览
本篇定位
从 9 到 21,Java 改成了"半年一小版本、三年一 LTS"的发版节奏(8/11/17/21 是 LTS)。本篇不逐条罗列 JEP,只挑日常写代码真正会用到的,每个特性讲"解决什么 + 语法长什么样"。入门级的新特性(Lambda/Stream)见 07-Lambda与函数式接口 及其后篇。
一、var:局部变量类型推断(JDK 10)
var list = new ArrayList<String>(); // 右边类型一目了然,左边免写
var map = new HashMap<String, Integer>();
规则与边界:仅限局部变量(方法内),且必须声明时初始化;字段、方法参数、返回类型都不能用 var。var list = new ArrayList<>() 会把元素推断成 Object,别在钻石符号里偷懒。
适合直觉判断:类型名太长重复、右边即自明。过度使用会牺牲可读性——var x = getResult() 没人知道 x 是什么。
二、文本块(JDK 15 正式):多行字符串
String json = """
{
"name": "Java",
"version": 21
}
""";
// 三个双引号包裹,内部引号不用转义,换行原样保留
解决 SQL、JSON、HTML 拼字符串的转义地狱。配合 \ 行末续行(下一行不换行)、\s 保留行尾空格。
三、record:不可变数据载体(JDK 16 正式)
public record Point(int x, int y) { } // 一行搞定
Point p = new Point(1, 2);
p.x(); // 访问器方法叫 x(),不是 getX()
p.equals(new Point(1, 2)); // true:自动实现 equals/hashCode/toString
编译器自动生成:私有的 final 字段、规范构造器、equals()/hashCode()/toString()、访问器方法。语义就是"不变的纯数据"——DTO、VO、返回值、Map key 的天然选择。
它可以有自定义成员:
public record Point(int x, int y) {
public static final Point ORIGIN = new Point(0, 0);
public double distanceFromOrigin() { return Math.hypot(x, y); } // 实例方法
}
与老方案对比:比手写 POJO 省 80% 模板代码;比 @Data 类的本质区别——record 字段真不可变(不可变对象线程安全、可放心当 Map key,见 01-封装 的"不可变对象")。
四、sealed:密封类(JDK 17 正式)
sealed interface Shape permits Circle, Rectangle { }
record Circle(double r) implements Shape { }
record Rectangle(double w, double h) implements Shape { }
// 未在 permits 列表里的类无法实现 Shape
控制"谁能继承/实现我",配合 switch 模式匹配做穷尽性检查——编译器知道所有子类,switch 就不用写 default。sealed 是继承的"白名单",与 final(全禁止)、open(全开放)构成继承控制的三档。
五、switch 表达式与模式匹配
箭头语法 + 返回值(JDK 14):
String desc = switch (day) {
case MONDAY, FRIDAY -> "忙";
case SATURDAY, SUNDAY -> "休";
default -> "普通";
};
instanceof 模式匹配(JDK 16):
if (obj instanceof String s && s.length() > 5) { // 一步完成类型判断+强转+命名
System.out.println(s);
}
老的写法 obj instanceof String + (String) obj 两步合成一步,且 s 的作用域精确到条件成立的分支。
switch 模式匹配(JDK 21):把上面两者结合,switch 直接匹配类型并解构 record:
static void describe(Object obj) {
switch (obj) {
case Point(int x, int y) -> System.out.println("点(" + x + "," + y + ")");
case String s -> System.out.println("字符串长度 " + s.length());
case null -> System.out.println("空");
default -> System.out.println("其他");
}
}
六、虚拟线程(JDK 21,LTS 重磅)
// 老:每请求一线程,平台线程昂贵(1MB 栈 + 系统调度)
// 新:虚拟线程由 JVM 调度,百万级并发廉价
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> System.out.println("轻量任务"));
}
Thread.ofVirtual().start(task); // 或直接起一个虚拟线程
解决"一连接一线程"模型下线程数上不去的问题:虚拟线程挂起时不占用 OS 线程,IO 密集场景可以轻松开几十万个。不是银弹——CPU 密集任务、synchronized 重的代码收益有限。现代 Web 框架(Spring Boot 3.2+)已原生支持。深入见 09-Java如何实现协程 与 00-Java并发总览.
七、其他值得知道的(选讲)
- 集合工厂方法(JDK 9):
List.of(1,2,3)/Map.of("a",1,"b",2),不可变集合的简洁创建。已见 00-容器总览。 - Stream API 增强(JDK 9/10/16):
takeWhile/dropWhile(顺序流快捷键)、Stream.toList()(不可变收集)。 String.strip()(JDK 11):trim只去 ASCII 空格,strip按 Unicode 处理;isBlank()(JDK 11)、lines()(JDK 11,按行拆分)。instanceof+ record 的 getter 命名:record 访问器就是x(),没有getX()——记住这点少踩坑。Files.readString/writeString(JDK 11):文件读写一次搞定,见 09-IO流体系。- Integer/Double 的
compareUnsigned、Math.clamp(JDK 21):零散小工具。
八、版本演进一句话地图
JDK 9 模块化(JPMS) + 集合工厂方法 + 私有接口方法
JDK 10 var 局部变量推断
JDK 11 LTS:String.strip/isBlank/lines + java.net.http 标准 HttpClient + ZGC
JDK 12-13 switch 表达式预览
JDK 14 switch 表达式正式 + instanceof 模式匹配预览
JDK 15 文本块正式 + ZGC 正式
JDK 16 record 正式
JDK 17 LTS:sealed + 文本块/switch 稳定
JDK 21 LTS:虚拟线程 + switch 模式匹配 + 分代 ZGC
选版本的口径:新项目起步直接 17 或 21 LTS;老项目 8 迁 17(先清 Unsafe/反射/JDK 内部 API 的使用——8 到 9 的模块化是最难跨的一步)。
高频面试题速答
- 你在用什么版本?为什么? 21 LTS——虚拟线程、record、switch 模式匹配都稳定了,长期支持。
- record 和 POJO 区别? record 字段 final、自动生成 equals/hashCode/toString、访问器无 get 前缀、不可继承;POJO 可变、模板多。
- sealed 有什么用? 限制继承白名单,配合 switch 做穷尽匹配,编译器帮你查漏。
- 虚拟线程能替代线程池吗? 能替代"为 IO 并发开的平台线程池",但 CPU 密集、需严格控制并发度的场景仍要
Executors.newFixedThreadPool。
勾连
- 主线的并发知识:00-Java并发总览
- Lambda/Stream 的内容:08-Stream与Optional
- switch 传统用法与支持类型:04-其他关键字
- 深挖 JDK 内部机制:01-Class对象与反射机制
💬 评论