Java 11-21 新特性盘点:var、record、sealed、虚拟线程
Java 从 11 到 21 发生了翻天覆地的变化。从 var 局部变量推断到虚拟线程,每一个特性都在让 Java 更现代、更高效。本文盘点这 10 个版本的核心特性。
一、各版本特性全景
scss
┌─────────────────────────────────────────────────────────┐
│ Java 11-21 核心特性演进 │
├─────────────────────────────────────────────────────────┤
│ │
│ Java 11 (LTS) ── var 局部变量推断、HTTP Client、 │
│ String API 增强、单文件程序 │
│ │
│ Java 14 ── Switch 表达式(标准)、record 预览 │
│ │
│ Java 15 ── 文本块(标准)、密封类预览、ZGC 生产级 │
│ │
│ Java 16 ── record(标准)、Pattern Matching 预览 │
│ │
│ Java 17 (LTS) ── 密封类(标准)、Pattern Matching for │
│ instanceof(标准)、强封装 │
│ │
│ Java 19 ── 虚拟线程(预览)、结构化并发(预览) │
│ │
│ Java 21 (LTS) ── 虚拟线程(标准)、Pattern Matching for │
│ switch(标准)、序列集合、记录模式 │
│ │
└─────────────────────────────────────────────────────────┘
二、var 局部变量类型推断(Java 11)
java
// Java 10 引入,Java 11 作为 LTS 包含
// 之前
Map<String, List<User>> users = new HashMap<>();
Stream<String> stream = users.keySet().stream();
// 现在
var users = new HashMap<String, List<User>>();
var stream = users.keySet().stream();
// 类型自动推断,代码更简洁
java
// var 只能用于局部变量,不能用于字段、方法参数、返回值
// ✅ 可以
var list = new ArrayList<String>();
var map = new HashMap<String, User>();
var result = process(data);
var stream = list.stream().filter(x -> x > 0).map(x -> x * 2);
// ❌ 不可以
// private var field = "hello"; // 字段
// public var process() { return 1; } // 返回值
// public void test(var param) { } // 参数
// 注意: var 不是动态类型,编译期已确定类型
var x = 1; // x 是 int
x = "hello"; // 编译错误! x 是 int 不能赋 String
三、record 记录类(Java 16 标准)
java
// 之前: 写一个数据类需要大量样板代码
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
@Override
public boolean equals(Object o) { /* ... */ }
@Override
public int hashCode() { /* ... */ }
@Override
public String toString() { /* ... */ }
}
// 现在: 一行搞定
public record Point(int x, int y) {}
// 自动生成: 构造器、getter(x(), y())、equals、hashCode、toString
java
// record 特性
record User(String name, int age, String email) {
// 紧凑构造器 (验证逻辑)
public User {
if (age < 0) throw new IllegalArgumentException("age");
if (email == null) email = "";
}
// 自定义方法
public boolean isAdult() {
return age >= 18;
}
// 不可变: 没有 setter
// 继承: record 隐式 final,不能被继承
// 实现接口: 可以
}
// 使用
User user = new User("Tom", 20, "tom@test.com");
user.name(); // "Tom" (注意不是 getName())
user.age(); // 20
user.isAdult(); // true
user.equals(new User("Tom", 20, "tom@test.com")); // true
arduino
record vs 传统数据类:
┌──────────────┬───────────────┐
│ record │ 传统 class │
├──────────────┼───────────────┤
│ 不可变 │ 可变/不可变 │
│ 自动生成方法 │ 手动编写 │
│ 隐式 final │ 可继承 │
│ 不能继承 │ 可以继承 │
│ 可实现接口 │ 可实现接口 │
│ 适合 DTO │ 适合实体类 │
└──────────────┴───────────────┘
四、sealed 密封类(Java 17 标准)
java
// 密封类: 控制哪些类可以继承/实现
public sealed interface Shape
permits Circle, Square, Rectangle {}
// 子类必须是 final、sealed 或 non-sealed
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}
non-sealed class Rectangle implements Shape {}
// Rectangle 的子类不受限制
// ❌ 其他类不能实现 Shape
// record Triangle(double base, double height) implements Shape {}
// 编译错误: Triangle 不在 permits 列表中
java
密封类层级:
sealed interface Shape
├── permits Circle → record (隐式 final)
├── permits Square → record (隐式 final)
└── permits Rectangle → non-sealed (可被任意继承)
├── CustomRectangle
└── RoundedRectangle
密封类的价值:
├── 编译期知道所有子类 → 支持穷尽检查
├── Pattern Matching switch 可以省略 default
└── 防止滥用继承,控制类型层次
五、Pattern Matching(Java 17+ 21)
1. instanceof 模式匹配(Java 17 标准)
java
// 之前
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
// 现在: 直接绑定变量
if (obj instanceof String s) {
System.out.println(s.length());
// s 在此作用域内可用
}
// 作用域: 流敏感
if (obj instanceof String s && s.length() > 5) {
// s 在 && 右侧已可用
System.out.println(s);
}
if (!(obj instanceof String s)) {
// s 不可用 (取反)
return;
}
// s 可用 (此处已确保是 String)
System.out.println(s);
2. switch 模式匹配(Java 21 标准)
java
// 传统 switch: 只能匹配常量
switch (day) {
case MONDAY: case FRIDAY: return 5;
case TUESDAY: case THURSDAY: return 6;
default: return 0;
}
// 新 switch: 模式匹配 + 表达式
String formatted = switch (obj) {
case Integer i -> "int: " + i;
case String s -> "str: " + s;
case null -> "null";
default -> "unknown";
};
// 配合密封类: 穷尽检查
sealed interface Shape permits Circle, Square, Rectangle {}
double area = switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Square s -> s.side() * s.side();
case Rectangle r -> r.width() * r.height();
// 不需要 default! 编译器知道已穷尽所有情况
};
3. 记录模式(Java 21)
java
// 解构 record
record Point(int x, int y) {}
record Line(Point start, Point end) {}
void printLine(Object obj) {
if (obj instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
System.out.printf("(%d,%d) -> (%d,%d)%n", x1, y1, x2, y2);
}
}
// 嵌套解构
if (obj instanceof Line(Point(var x1, var y1), Point(var x2, var y2))) {
double length = Math.sqrt(Math.pow(x2-x1, 2) + Math.pow(y2-y1, 2));
}
// switch 中的记录模式
String describe = switch (shape) {
case Circle(double r) when r > 100 -> "large circle";
case Circle(double r) -> "small circle, r=" + r;
case Square(double s) -> "square, side=" + s;
};
六、文本块(Java 15 标准)
java
// 之前: 大量转义和拼接
String json = "{\n" +
" \"name\": \"Tom\",\n" +
" \"age\": 20\n" +
"}";
// 现在: 文本块
String json = """
{
"name": "Tom",
"age": 20
}
""";
// 可选的转义处理
String sql = """
SELECT * FROM users \
WHERE age > 18 \
ORDER BY name
"""; // \ 表示行连接(续行)
java
// 字符串模板 (Java 21 预览, 后续版本移除)
// 注意: 字符串模板在 Java 21 预览后被撤回, 不推荐使用
// var name = "Tom";
// var msg = STR."Hello \{name}"; // 预览特性,暂不稳定
七、虚拟线程(Java 21 标准)
java
// 虚拟线程: 轻量级线程,由 JVM 调度,不是 OS 线程
// 创建平台线程 (传统)
Thread thread = new Thread(() -> {
System.out.println("Platform Thread: " + Thread.currentThread());
});
// 创建虚拟线程
Thread vThread = Thread.ofVirtual().start(() -> {
System.out.println("Virtual Thread: " + Thread.currentThread());
});
// 通过 Executors
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
} // 10000 个虚拟线程,几乎不消耗额外内存
less
平台线程 vs 虚拟线程:
┌──────────────────┬──────────────────┐
│ 平台线程 │ 虚拟线程 │
├──────────────────┼──────────────────┤
│ OS 线程 (1:1) │ JVM 调度 (N:M) │
│ 栈 ~1MB │ 栈 ~KB 级 │
│ 创建开销大 │ 创建开销极小 │
│ 数量受 OS 限制 │ 数量可达百万级 │
│ 阻塞→OS 切换 │ 阻塞→JVM 切换 │
│ 适合 CPU 密集 │ 适合 IO 密集 │
└──────────────────┴──────────────────┘
虚拟线程执行模型:
虚拟线程#1 ─┐
虚拟线程#2 ─┤ JVM 调度器 (ForkJoinPool)
虚拟线程#N ─┘ │
▼
平台线程 (Carrier)
┌─────┬─────┬─────┐
│ CT1 │ CT2 │ CT3 │ ← 少量载体线程
└─────┴─────┴─────┘
虚拟线程在 IO 阻塞时:
1. JVM 将虚拟线程从载体线程卸载
2. 载体线程可以执行其他虚拟线程
3. IO 完成后, 虚拟线程重新挂载到载体线程
虚拟线程的适用场景和限制
java
// ✅ 适合: IO 密集型
Thread.ofVirtual().start(() -> {
var response = httpClient.send(request, BodyHandlers.ofString()); // 阻塞 IO
process(response);
});
// ✅ 适合: 高并发 Web 服务器
// Spring Boot 3.2+ 支持虚拟线程
// spring.threads.virtual.enabled=true
// ❌ 不适合: CPU 密集型
Thread.ofVirtual().start(() -> {
// 计算密集任务不会释放载体线程
// 虚拟线程优势消失
heavyComputation();
});
// ⚠️ 注意: synchronized 会 pin 虚拟线程
// 虚拟线程在 synchronized 块中阻塞时不能被卸载
// 建议: 用 ReentrantLock 替代 synchronized
Thread.ofVirtual().start(() -> {
// ❌ synchronized 会 pin 虚拟线程
synchronized(lock) {
blockingIO(); // 载体线程被占用!
}
// ✅ ReentrantLock 不会 pin
lock.lock();
try {
blockingIO(); // 虚拟线程可卸载
} finally {
lock.unlock();
}
});
八、其他重要特性
Switch 表达式(Java 14 标准)
java
// switch 表达式 + 箭头语法
int days = switch (month) {
case JANUARY, MARCH, MAY, JULY, AUGUST, OCTOBER, DECEMBER -> 31;
case APRIL, JUNE, SEPTEMBER, NOVEMBER -> 30;
case FEBRUARY -> isLeapYear ? 29 : 28;
default -> throw new IllegalArgumentException();
};
// yield 返回值
int result = switch (x) {
case 1, 2, 3 -> x * 10;
default -> {
log("default: " + x);
yield x * 100;
}
};
序列集合(Java 21)
java
// SequencedCollection: 有明确遇见顺序的集合
interface SequencedCollection<E> extends Collection<E> {
E getFirst(); // 第一个元素
E getLast(); // 最后一个元素
void addFirst(E e); // 头部添加
void addLast(E e); // 尾部添加
E removeFirst(); // 移除第一个
E removeLast(); // 移除最后一个
SequencedCollection<E> reversed(); // 反转视图
}
// List, Deque 等都是 SequencedCollection
List<Integer> list = List.of(1, 2, 3, 4, 5);
list.getFirst(); // 1
list.getLast(); // 5
list.reversed(); // [5, 4, 3, 2, 1]
LinkedHashSet<Integer> set = new LinkedHashSet<>(List.of(1, 2, 3));
set.getFirst(); // 1 (LinkedHashSet 是 SequencedSet)
结构化并发(Java 21 预览)
java
// 结构化并发: 父子线程生命周期绑定
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// 并发执行多个子任务
Subtask<User> userTask = scope.fork(() -> fetchUser(id));
Subtask<Order> orderTask = scope.fork(() -> fetchOrder(id));
scope.join(); // 等待所有子任务完成
scope.throwIfFailed(); // 任一失败则抛异常
// 所有成功
User user = userTask.get();
Order order = orderTask.get();
return new UserOrder(user, order);
} // 退出 scope 时自动关闭所有子任务
九、各版本特性速查表
| 版本 | 特性 | 语法 |
|---|---|---|
| Java 11 | var | var x = 1; |
| Java 14 | switch 表达式 | var r = switch(x) { case 1 -> "a"; }; |
| Java 15 | 文本块 | """text""" |
| Java 16 | record | record Point(int x, int y) {} |
| Java 17 | sealed | sealed interface Shape permits Circle |
| Java 17 | instanceof PM | if (obj instanceof String s) |
| Java 21 | 虚拟线程 | Thread.ofVirtual().start(r) |
| Java 21 | switch PM | case Integer i -> ... |
| Java 21 | 记录模式 | case Point(var x, var y) |
| Java 21 | 序列集合 | list.getFirst() |
十、常见面试追问
Q1:record 和 Lombok @Data 有什么区别?
record 是语言级特性,生成的类不可变且隐式 final。Lombok @Data 是编译期字节码注入,生成的是可变类(有 setter),且类可以被继承。record 适合 DTO,Lombok @Data 适合实体类。
Q2:sealed 类有什么实际用途?
- 配合 Pattern Matching switch 实现穷尽检查(不需要 default)
- 领域建模时控制继承层次
- 编译器可检查遗漏的 case
Q3:虚拟线程能完全替代线程池吗?
不能完全替代。虚拟线程适合 IO 密集型场景,CPU 密集型任务仍应使用平台线程。而且虚拟线程本身不限制并发数,仍需要信号量控制对共享资源的访问。
Q4:虚拟线程和 Reactor/CompletableFuture 怎么选?
虚拟线程用同步代码风格实现异步效果,代码更简单可读。CompletableFuture/Reactor 更灵活(组合、超时、回调),但编程复杂。新项目建议优先用虚拟线程。
十一、总结
java
Java 11-21 核心演进:
语法简化: var → record → sealed → text block
类型安全: Pattern Matching (instanceof → switch → record pattern)
并发革命: 虚拟线程 (同步代码风格 + 异步性能)
集合增强: 序列集合 (getFirst/getLast/reversed)
设计哲学:
record: 数据类不可变 + 样板代码消除
sealed: 控制继承 + 编译期穷尽检查
虚拟线程: 同步编程模型 + 高并发能力
Pattern Matching: 类型安全 + 代码简化
Java 11-21 的演进方向清晰:减少样板代码、增强类型安全、简化并发编程。从 record 到虚拟线程,每个特性都在让 Java 更现代化,同时也保持了向后兼容的核心承诺。