Lesson 51 · Java 高级特性
Java 8 → 21 核心特性速览:var、record、sealed、虚拟线程
Java 半年一版本,哪些特性真正值得学?
2017 年 9 月,Oracle 宣布 Java 采用每 6 个月一个版本的新发布节奏,同时引入 LTS(长期支持)概念:只有 LTS 版本能获得长达数年的商业支持。到目前为止,关键的 LTS 版本有三个:
| 版本 | 发布时间 | 定位 | 支持周期 |
|---|---|---|---|
| Java 8 | 2014.03 | 函数式编程元年 | Oracle 商业支持至 2030+ |
| Java 11 | 2018.09 | 首个新节奏 LTS | 支持至 2026+ |
| Java 17 | 2021.09 | Spring Boot 3 最低要求 | 支持至 2029+ |
| Java 21 | 2023.09 | 虚拟线程改变并发模型 | 支持至 2031+ |
非 LTS 版本(如 9、10、12~16、18~20)中的特性会被"顺延"到下一个 LTS 中正式落地。因此我们按实际影响力和生产可用性来筛选,只讲面试和实战中真正高频的特性。
面试官不期望你背出每个版本的所有 JEP 编号,而是想看你能否说出每个特性的动机、用法和取舍。能对比 record vs Lombok、虚拟线程 vs 协程的候选人,分数会明显更高。
Java 8(LTS):函数式编程的起点
Java 8 是整个 Java 生态的分水岭。它引入的函数式特性让 Java 从纯面向对象语言变成面向对象 + 函数式的混合范式。即使过了十年,这五个特性仍然是面试必考项。
// 1. Lambda:告别匿名内部类 List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.sort((a, b) -> a.length() - b.length()); // 2. Stream:声明式数据处理管线 List<String> result = names.stream() .filter(n -> n.length() > 3) .map(String::toUpperCase) .collect(Collectors.toList()); // 3. Optional:告别 NullPointerException Optional<User> user = findById(42); String name = user.map(User::getName).orElse("unknown");
Java 8 另外两个高频考点:
| 特性 | 要点 | 面试陷阱 |
|---|---|---|
| Date/Time API (java.time) | LocalDate、LocalTime、LocalDateTime 等不可变对象,线程安全,替代 Date/Calendar | Date 是可变且非线程安全的,SimpleDateFormat 也是——这是经典坑 |
| default 方法 | 接口可以有默认实现,解决了接口演化问题 | 菱形继承问题:两个接口都定义了同名 default 方法时,实现类必须显式覆写 |
Java 8 的五大特性(Lambda、Stream、Optional、Date/Time、default 方法)是所有后续特性的基石。如果你对这些还不够熟练,应该先巩固它们再往后学。
Java 10-11:var 推断与 HTTP Client
Java 10 是非 LTS 版本,但它引入的 var 类型推断被保留到了所有后续版本中,成为开发者使用频率最高的语法糖之一。Java 11 作为 LTS 版本则带来了标准化的 HTTP Client。
// Java 10+: 编译器自动推断类型 var list = new ArrayList<String>(); // 推断为 ArrayList<String> var map = new HashMap<String, Integer>(); // 推断为 HashMap<String, Integer> var path = Paths.get("/tmp/data.csv"); // 推断为 Path // ⚠️ var 只能用于局部变量,不能用于: // - 方法参数、返回值 // - 类字段 // - 没有初始化器的变量
new ArrayList<User>()),用 var 反而减少噪音。但如果右侧是一个方法调用,如 var result = process(),你就丢失了类型信息——此时应该写明确类型。Java 10 还引入了 List.of()、Set.of()、Map.of() 等不可变集合工厂方法,配合 var 使用尤其简洁:
// Java 9+ 不可变集合工厂方法 var colors = List.of("red", "green", "blue"); var config = Map.of( "timeout", 30, "retries", 3 ); // 返回的是不可变集合,调用 add() 会抛 UnsupportedOperationException // 如果需要可变的,用 new ArrayList<>(List.of(...))
Java 11 的亮点特性:
// 终于不用再依赖 Apache HttpClient 或 OkHttp 了 var client = HttpClient.newHttpClient(); var request = HttpRequest.newBuilder() .uri(URI.create("https://api.example.com/users")) .header("Accept", "application/json") .GET() .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode()); // 200 System.out.println(response.body()); // JSON 字符串
Java 11 还给 String 加了几个实用方法:
| 方法 | 用途 | 示例 |
|---|---|---|
isBlank() | 判断是否为空或纯空白 | " ".isBlank() → true |
strip() | 去除首尾空白(支持 Unicode 空白) | 比 trim() 更准确 |
lines() | 按行拆分为 Stream | "a\nb\nc".lines().count() → 3 |
repeat(n) | 重复拼接 n 次 | "ha".repeat(3) → "hahaha" |
Java 14-17(LTS):Record、Sealed、Pattern Matching
Java 17 是 Spring Boot 3 的最低版本要求,也是目前企业升级的主流目标。从 Java 14 到 17,四个特性经历了 preview → 正式发布的完整过程,设计质量非常高。
// 一行代码 = getter + equals + hashCode + toString + 不可变 public record Point(int x, int y) {} // 编译器自动生成: // - private final int x, y // - 全参构造函数 // - x(), y() 访问器(注意:不是 getX()) // - equals / hashCode / toString var p = new Point(3, 4); System.out.println(p.x()); // 3 System.out.println(p); // Point[x=3, y=4] // 可以添加 compact constructor 做校验 public record User(String name, int age) { public User { if (age < 0) throw new IllegalArgumentException("age < 0"); } }
Record 是语言级特性(JVM 直接支持),Lombok 是编译期注解处理器。Record 保证不可变(字段 final),Lombok 的 @Data 生成可变类。如果你的 DTO 不需要 setter,Record 是更好的选择;如果需要 mutable bean,Lombok 仍然有优势。
// 传统 switch:case 穿透、忘记 break 就出 bug // 新 switch 表达式:有返回值,箭头语法无穿透 var label = switch (day) { case MONDAY, FRIDAY -> "工作日"; case TUESDAY -> "周二"; case SATURDAY, SUNDAY -> "周末"; default -> "其他"; }; // 不需要 break,不可能穿透,直接赋值给变量
// 限制哪些类可以继承——实现"代数数据类型" public sealed interface Shape permits Circle, Rectangle, Triangle {} public record Circle(double radius) implements Shape {} public record Rectangle(double w, double h) implements Shape {} public record Triangle(double base, double height) implements Shape {} // sealed + record 组合:编译器知道所有子类型 // 为 Java 21 的 switch 模式匹配铺路
同期上线的其他实用特性:
| 特性 | 版本 | 一句话 |
|---|---|---|
| Text Blocks | Java 15 | """ 三引号多行字符串,写 SQL/JSON 不用再拼 + |
| instanceof 模式匹配 | Java 16 | if (obj instanceof String s) 直接拿到变量 s |
| Helpful NPE | Java 14 | NPE 信息精确到"哪个字段是 null",不再只报行号 |
Java 21(LTS):虚拟线程改写并发规则
Java 21 是近十年来最重要的 Java 版本,没有之一。虚拟线程(Virtual Threads)从根本上改变了 Java 的并发编程模型,让"一个请求一个线程"的简单模型重新变得可行。
// 传统方式:平台线程,每个线程 ~1MB 栈内存 // 10000 个线程 = 10GB 内存——这就是"线程很贵"的原因 // Java 21:虚拟线程,极其轻量 try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 100_000).forEach(i -> executor.submit(() -> { Thread.sleep(Duration.ofSeconds(1)); return i; }) ); } // 10 万个虚拟线程,内存占用可能不到 100MB // 因为虚拟线程不绑定 OS 线程,在阻塞时自动让出
// sealed + record + switch 模式匹配 = 穷举检查 double area = switch (shape) { case Circle c -> Math.PI * c.radius() * c.radius(); case Rectangle r -> r.w() * r.h(); case Triangle t -> 0.5 * t.base() * t.height(); // 无需 default!编译器知道 sealed 只有这三种 };
Java 21 的其他亮点:
| 特性 | 状态 | 说明 |
|---|---|---|
| Sequenced Collections | 正式 | 统一了 List、Deque、SortedSet 的顺序访问接口:getFirst()、getLast()、reversed() |
| Structured Concurrency | Preview | 将多个并发任务视为一个单元,统一取消、统一异常处理 |
| Scoped Values | Preview | ThreadLocal 的替代品,为虚拟线程优化,不可变且更安全 |
| String Templates | Preview | 类似 Kotlin 的字符串模板 STR."Hello \{name}" |
Java 21 最值得关注的特性排序:虚拟线程 > switch 模式匹配 > Sequenced Collections。虚拟线程是真正改变架构的特性,后两个是提升编码体验的语法糖。
虚拟线程深度解析:它是如何工作的?
虚拟线程的核心工作原理可以用三个关键词概括:
| 机制 | 说明 |
|---|---|
| Carrier Thread(载体线程) | 底层是一个 ForkJoinPool,线程数默认等于 CPU 核心数。虚拟线程并不直接跑在 OS 上,而是被调度到载体线程上执行 |
| Mount(挂载) | 虚拟线程开始执行时,被"挂载"到一个载体线程上。此时它和普通线程一样运行 |
| Unmount(卸载) | 当虚拟线程遇到阻塞操作(I/O、sleep、lock)时,JVM 自动将其从载体线程上"卸载",载体线程立刻去执行下一个虚拟线程。阻塞结束后,虚拟线程重新排队等待 mount |
// 平台线程(OS Thread) // - 1:1 映射到 OS 线程 // - 栈内存 ~1MB,创建/切换开销大 // - 通常线程池限制在几百到几千 // 虚拟线程(Virtual Thread)—— Java 的方式 // - M:N 调度:M 个虚拟线程复用 N 个载体线程 // - 栈内存 ~几KB(存在 JVM 堆上,可被 GC) // - 仍然是抢占式调度(不是协作式) // 协程(Kotlin / Go) // - 也是 M:N 调度,但使用协作式挂起(suspend) // - 需要改写代码为 suspend/async 风格 // - Java 虚拟线程不需要改代码——所有阻塞自动处理
适合:I/O 密集型应用(HTTP 调用、数据库查询、文件读写)。虚拟线程在阻塞等待 I/O 时会自动让出载体线程,吞吐量可以提升数倍。
不适合:CPU 密集型计算。虚拟线程的载体线程池大小等于 CPU 核心数,纯计算场景下没有收益,还有调度开销。另外,synchronized 块和 native 方法会导致"pinned"(钉住)问题——载体线程无法被释放。此时应改用 ReentrantLock。
避免场景 = CPU 密集型 + synchronized 热点 + JNI native 调用
总结:特性采用优先级与时间线
面对这么多新特性,企业和个人应该按什么顺序升级?以下是一张优先级表:
| 优先级 | 特性 | 版本 | 采用理由 |
|---|---|---|---|
| P0 | Lambda + Stream | 8 | 已成行业标准,不用就落后 |
| P0 | Optional + Date/Time | 8 | 消灭 NPE 和时间处理 bug |
| P1 | var 类型推断 | 10 | 减少样板代码,零运行时开销 |
| P1 | Text Blocks | 15 | SQL、JSON、测试数据编写体验飞跃 |
| P1 | Record | 16 | 替代 Lombok 做 DTO/VO,更安全 |
| P2 | Sealed Classes | 17 | 配合 Record + Switch 实现穷举模式 |
| P2 | Switch 模式匹配 | 21 | 替代 instanceof 链,编译器穷举检查 |
| P0+ | 虚拟线程 | 21 | 颠覆并发模型,I/O 密集场景必用 |
全文回顾
- Java 8 是函数式编程的基础——Lambda、Stream、Optional 至今仍是使用频率最高的特性
- Java 10-11 带来了 var 和 HTTP Client,减少了样板代码
- Java 14-17 通过 Record、Sealed、Switch 表达式构建了"代数数据类型"三件套
- Java 21 的虚拟线程是近十年最重要的特性,让 Java 在 I/O 密集场景下重获简洁的同步编程模型
- 升级建议:先巩固 Java 8 → 升级 Java 17 用 Record + Sealed → 评估 Java 21 虚拟线程
面试时不要罗列特性清单,而是围绕一个核心叙事来组织答案:Java 的演进方向是"减少样板代码 + 提升并发能力"。从 Lambda 到 Record 是在减少样板代码,从平台线程到虚拟线程是在提升并发能力。能把这两条线讲清楚,你的回答就有深度。