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 的 compareUnsignedMath.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 的模块化是最难跨的一步)。

高频面试题速答

  1. 你在用什么版本?为什么? 21 LTS——虚拟线程、record、switch 模式匹配都稳定了,长期支持。
  2. record 和 POJO 区别? record 字段 final、自动生成 equals/hashCode/toString、访问器无 get 前缀、不可继承;POJO 可变、模板多。
  3. sealed 有什么用? 限制继承白名单,配合 switch 做穷尽匹配,编译器帮你查漏。
  4. 虚拟线程能替代线程池吗? 能替代"为 IO 并发开的平台线程池",但 CPU 密集、需严格控制并发度的场景仍要 Executors.newFixedThreadPool

勾连


⬅️ 02-内部类 🏠 00-Java ➡️ 04-设计模式体系