Java新特性
1. 概览
从 Java 8 到现在的 Java 21+,Java 经历了从“老牌稳重”到“现代高效”的巨大转变。为了帮你快速建立直觉,我把这些版本划分为几个关键的里程碑:
1.1 史诗级里程碑:Java 8 (2014)
这是目前国内面试和老项目中最核心的版本。它改变了 Java 的编程范式,引入了函数式编程。
- Lambda 表达式:让匿名内部类变得极简,像写函数一样写代码。
- Stream API:像操作 SQL 一样处理集合数据(filter, map, collect)。
- Optional:官方优雅解决
NullPointerException(空指针)的方案。 - 新的日期时间 API:引入了
LocalDateTime等,取代了难用的Date。
1.2 模块化革命:Java 9 - 11 (LTS)
从这个阶段开始,Java 进入了每半年更新一次的节奏,Java 11 是继 8 之后的第二个长期支持版本(LTS)。
- 模块系统 (Jigsaw):Java 9 引入,旨在让 JDK 瘦身,解决复杂的 Jar 包依赖冲突。
- var 局部变量类型推断:Java 10 引入,写代码快了,比如
var list = new ArrayList<String>();。 - HTTP Client:Java 11 引入了官方的原生异步 HTTP 客户端,支持 HTTP/2。
- ZGC (实验性):Java 11 引入了全新的垃圾回收器,追求极低的停顿时间。
1.3 语法现代化:Java 12 - 17 (LTS)
Jva 开始借鉴 Scala 和 Kotlin,代码变得越来越简洁。Java 17 是目前新项目的首选 LTS 版本。
- Switch 表达式:Switch 可以直接返回值了(像表达式一样),且不再需要写一堆
break。 - Text Blocks (文本块):Java 15 正式引入。再也不用
+号拼接多行 SQL 或 JSON 了,直接用三个引号"""包裹。 - Record 类:Java 16 正式引入。正如我们刚才讨论的,一行代码搞定 DTO。
- Sealed Classes (密封类):Java 17 引入。可以限制哪些类能继承你的类,增强了代码的安全性。
1.4 极致性能与并发:Java 18 - 21 (LTS)
Java 21 是目前最新的长期支持版本,它带来了并发编程的革命。
- 虚拟线程 (Virtual Threads):Java 21 的重磅特性(Project Loom)。它极其轻量,一个 JVM 进程可以轻松开启百万级的线程,彻底改变了高并发开发的模式,性能大幅提升。
- 模式匹配 (Pattern Matching):增强了
instanceof和switch,可以直接在判断时解构数据。 - 分代 ZGC:大幅优化了 ZGC 的性能,在大内存环境下表现极其出色。
1.5 总结:Ethan 应该关注哪些?
- 面试必看:Java 8 (Stream, Lambda) 和 Java 17 (Record, Sealed Classes)。
- 进阶必学:Java 21 的虚拟线程(这会是未来几年高性能后端架构的标配)。
- 开发习惯:习惯使用
var、Text Blocks和Record,这能让你的代码看起来像 2026 年的程序员写的,而不是 2014 年的。
2. 特别讲解
2.1 Record
Java 14 引入(Java 16 正式发布)的一个新特性:Record(数据载体类)。
2.1.1 写法
public record ApiResponse<T>(String code, String message, T data) {
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>("SUCCESS", "OK", data);
}
public static <T> ApiResponse<T> error(String code, String message) {
return new ApiResponse<>(code, message, null);
}
}简单来说,它是一个特殊的类。它的核心目的是用最简洁的代码来定义一个“只负责保存数据”的对象(也就是我们常说的 DTO、VO 或 POJO)。
2.1.2 和普通 class 类的区别
| 特性 | 普通 class | record 类 |
|---|---|---|
| 全参构造函数 | 需手动写或用 Lombok | 自动生成 |
| Getter 方法 | 需手动写(getXXX) | 自动生成(字段同名) |
| toString() | 默认输出地址(无用) | 自动输出所有字段值 |
| equals/hashCode | 默认比较内存地址 | 自动按字段内容比较 |
| 继承性 | 可继承或被继承 | final 类,不可继承 |
2.1.3 快速决策清单
这个类是不是只用来装数据的? Yes
这个类创建后需不需要改里面的值? No
这个类需不需要继承体系? No
这是一个数据库映射实体吗? No
如果以上都满足,果断使用
record!
2.2 文本块 Text Blocks (Java 15)
String query = """
SELECT * FROM user
WHERE id = ? AND status = 'ACTIVE'
ORDER BY create_time DESC
""";2.3 Switch 表达式增强 (Java 12/14)
增强 Switch:使用 -> 符号,自带 break 效果,且支持多值匹配。
// 直接赋值给变量
String dayType = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> "Feeling Good";
case TUESDAY, THURSDAY -> "Working Hard";
default -> {
// 支持代码块,通过 yield 返回值
String res = "Midweek";
yield res;
}
};2.4 Sealed Classes 密封类 (Java 15/17)
这是对 Java 继承体系的一次精准控制。
痛点:以前如果你想让一个类不被继承,只能用
final。但如果你想让类 A 只能被 B、C 继承,不能被 D 继承,Java 做不到。解决方案:使用
sealed声明类,并用permits列出“特许”的子类。java// 定义一个密封接口:只允许文本消息和图片消息 public sealed interface Message permits TextMessage, ImageMessage {} public final class TextMessage implements Message { ... } public final class ImageMessage implements Message { ... }实战价值:这在写状态机或领域模型时非常强大。当你配合
switch使用时,编译器能检查你是否漏掉了某种子类情况(穷举性检查),安全性直接拉满。
2.5 虚拟线程 (Java 21)
提到 Java 21 的虚拟线程(Virtual Threads),这绝对是 Java 近十年来最重大的变革。它由 Project Loom 项目孵化,旨在彻底解决 Java 在高并发场景下的“昂贵”问题。
为了让你听得透彻,我们从为什么需要它、它是什么、以及怎么用三个维度来拆解。
2.5.1 痛点:传统的“平台线程”太贵了
在 Java 21 之前,Java 的 Thread 类实际上是对操作系统(OS)线程的一层薄薄封装。这种线程被称为“平台线程(Platform Threads)”。
- 1:1 模型:一个 Java 线程对应一个 OS 线程。
- 昂贵的内存:每个 OS 线程通常预留约 1MB 的栈内存。如果你想开 1 万个线程,就需要约 10GB 内存,这显然是不现实的。
- 昂贵的上下文切换:当 CPU 从一个线程切换到另一个线程时,需要陷入内核态,保存和恢复各种寄存器状态。如果线程极多,CPU 大量时间都花在“切来切去”上,而不是在跑你的代码。
- 阻塞即浪费:当线程执行 IO 操作(比如查数据库、调 AI 接口)时,这个昂贵的 OS 线程就只能在那儿干等,啥也干不了。
2.5.2 核心原理:虚拟线程是怎么玩的?
虚拟线程引入了 M:N 模型,即“大量”的虚拟线程映射到“少量”的平台线程上。
- 轻量级:虚拟线程不在 OS 级别创建,而是在 JVM 内存中创建。它的栈内存是按需增加的,起步只需要几百个字节。
- 非阻塞的奥秘:当一个虚拟线程执行阻塞 IO 时,JVM 会自动将该虚拟线程从平台线程上卸载(Unmount),把平台线程腾出来去跑别的虚拟线程。等 IO 返回了,JVM 再把这个虚拟线程挂载(Mount)回某个空闲的平台线程继续运行。
- 开发者透明:对你来说,你写的还是同步阻塞代码,不需要写复杂的
CompletableFuture或响应式代码,但底层却跑出了极高的并发效率。
2.5.3 如何使用?
Java 21 保持了向后兼容性,你可以像创建普通线程一样创建它。
方式 A:直接创建
Thread vThread = Thread.ofVirtual().name("ai-worker").unstarted(() -> {
// 执行耗时 IO,比如调用 OpenRouter
System.out.println("Running in virtual thread");
});
vThread.start();方式 B:使用 ExecutorService (最常用)
这是你以后在 Spring Boot 3 项目中最可能看到的写法:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
// 每个任务都会开一个新的虚拟线程,开 10 万个也没压力
});
}2.5.4 关键点:虚拟线程不是万能药
Ethan,在使用时一定要注意以下两点:
不要池化 (Don't Pool):
以前我们用线程池是为了复用昂贵的 OS 线程。但虚拟线程随用随建,用完即丢,不需要线程池。如果你给虚拟线程建个池子,反而限制了它的发挥。
IO 密集型 vs CPU 密集型:
- IO 密集型 (适用):查数据库、调接口、读写文件。虚拟线程能极大地提升吞吐量。
- CPU 密集型 (不适用):复杂的数学计算、视频编码。因为 CPU 核心数是固定的,虚拟线程再多也没用,这种场景老老实实用平台线程。
你可以简单地理解为:虚拟线程让 Java 拥有了像 Go 语言(Goroutine)一样的并发能力,但又保留了 Java 完善的生态系统。