Skip to content

JDK 12~16 新特性:语法糖大丰收

你有没有觉得 Java 语法太啰嗦?

每次写 instanceof 后还要强制类型转换,写个多行字符串要一堆转义符,switch 不加 break 就「穿透」到天荒地老——这些问题,从 JDK 12 开始,终于有了答案。


JDK 12:Switch 表达式登场

Switch 表达式(Preview → JDK 14 正式)

传统 switch 的痛点:忘记写 break,就变成了「贯穿」(Fall-Through):

java
// JDK 11 及之前
switch (day) {
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        System.out.println(6);
        break;  // 漏写就悲剧
    case TUESDAY:
        System.out.println(7);
        break;
    default:
        System.out.println(0);
}

JDK 12 引入箭头语法,简洁又安全:

java
// JDK 12+
int num = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;  // 多值合并,不需要 break
    case TUESDAY -> 7;
    case THURSDAY, SATURDAY -> 8;
    case WEDNESDAY -> 9;
    default -> 0;
};

两种语法对比

特性冒号语法箭头语法
break必须(否则穿透)不需要
多值合并逗号分隔逗号分隔
返回值需要变量赋值直接返回值
块语句不支持支持(可用 yield)

G1 改进

JDK 12 对 G1 垃圾收集器做了两项重要改进:

  1. NUMA 感知内存分配:优化多插槽服务器的内存分配
  2. Uncommit:立即释放未使用的堆内存给操作系统
bash
# JDK 12+ 自动启用未使用内存归还
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 myapp.jar

JDK 13:Text Block 与 yield

文本块(Text Block)Preview

写 JSON、SQL 的痛苦,谁写谁知道:

java
// JDK 12 及之前:到处都是转义符
String json = "{\n" +
    "  \"name\": \"Alice\",\n" +
    "  \"age\": 30\n" +
    "}";

// JDK 13+:所见即所得
String json = """
    {
      "name": "Alice",
      "age": 30
    }
    """;

语法规则

  • """ 开头,后面紧跟换行
  • 结尾的 """ 必须在单独一行
  • 自动处理缩进对齐
java
// 注意缩进对齐
String query = """
    SELECT id, name, email
    FROM users
    WHERE status = 'active'
    ORDER BY created_at DESC
    """;  // 前面空格会被自动去掉

yield 关键字

箭头表达式可以直接返回值,但如果逻辑复杂需要多行语句呢?

java
// 单行箭头:直接返回
case "A" -> 1;

// 多行逻辑:需要 yield
int result = switch (grade) {
    case "A" -> {
        if (score >= 95) {
            yield 100;
        }
        yield 90;
    }
    case "B" -> {
        yield 80;
    }
    default -> {
        System.out.println("Unknown grade");
        yield 0;
    }
};

yield vs return

  • return 是方法返回
  • yield 是 switch 表达式返回值

JDK 14:Record 与 Pattern Matching 预览

instanceof 模式匹配(Preview → JDK 16 正式)

java
// JDK 13 及之前
if (obj instanceof String) {
    String s = (String) obj;  // 强制转换
    System.out.println(s.length());
}

// JDK 14+:模式变量自动声明
if (obj instanceof String s) {
    System.out.println(s.length());  // 直接用 s
}

结合条件判断

java
// JDK 16+
if (obj instanceof String s && s.length() > 5) {
    System.out.println(s.toUpperCase());
}
// 短路求值保证安全

Records 预览(Preview → JDK 16 正式)

POJO 类的样板代码,终于可以简化了:

java
// JDK 15 及之前
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) {
        if (this == o) return true;
        if (!(o instanceof Point)) return false;
        Point p = (Point) o;
        return x == p.x && y == p.y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}

// JDK 16+:一行搞定
public record Point(int x, int y) {}

自动生成的内容

方法说明
构造函数Point(int x, int y)
getterx()y()(不是 getX())
equals()按值比较
hashCode()基于字段
toString()包含所有字段

JDK 15:ZGC 与 Sealed Classes

ZGC 生产可用

ZGC 从 JDK 11 实验阶段升级为生产可用:

bash
# JDK 15+:ZGC 生产可用
java -XX:+UseZGC -Xmx64g -jar application.jar

Sealed Classes 第二预览

密封类的概念在 JDK 15 二次预览(最终在 JDK 17 正式发布):

java
// JDK 17 正式语法
public sealed class Shape permits Circle, Rectangle, Square {}

// Circle 禁止进一步继承
public final class Circle extends Shape {}

// Rectangle 继续密封
public sealed class Rectangle extends Shape permits ColoredRectangle {}

// Square 打开继承
public non-sealed class Square extends Shape {}

CharSequence.isEmpty()

java
// JDK 15+
CharSequence seq = "Hello";
seq.isEmpty();  // false
// 之前只能用 String 的 isEmpty()

JDK 16:承前启后

JDK 16 是 JDK 17 LTS 的前身,引入多项正式版特性。

正式版特性

java
// 1. Records 正式版
public record User(String name, int age) {}

// 2. instanceof Pattern Matching 正式版
if (obj instanceof String s) {
    System.out.println(s.length());
}

// 3. 方向性方法引用(::)
// JDK 16+ 允许更灵活的 lambda 和方法引用转换

jpackage 打包工具

bash
# 创建平台原生安装包
jpackage --input path/to/input --main-jar myapp.jar \
    --name MyApp --type exe  # Windows
    # --type dmg             # macOS
    # --type deb             # Linux

Stream.toList() 简化

java
// JDK 16+:toList() 返回不可变列表
List<String> list = Stream.of("a", "b", "c")
    .filter(s -> s.length() > 1)
    .toList();  // 比 .collect(Collectors.toList()) 简洁

// JDK 15 及之前
List<String> list = Stream.of("a", "b", "c")
    .collect(Collectors.toList());

版本对比一览

特性JDK 12JDK 13JDK 14JDK 15JDK 16
Switch 表达式Preview正式
yield 关键字Preview正式
Text BlockPreview正式
instanceof PatternPreview正式
RecordsPreview2nd Preview正式
Sealed Classes2nd Preview
ZGC 生产

面试追问方向

  1. Switch 表达式的「穷尽性检查」是什么?

    • 编译器检查 switch 是否处理了所有可能值
    • 如果有遗漏且没有 default,编译报错
  2. Record 和普通类的区别是什么?

    • Record 是隐式 final 的
    • 不能声明实例字段(只能有组件)
    • 没有 setX() 方法,Record 是不可变的
  3. Text Block 的转义序列有哪些?

    • \n 新行
    • \t 水平制表符
    • \\ 反斜杠
    • \s 空格(保留行尾空格)
    • """ 三引号本身
  4. Sealed Class 的 permits 子类有什么限制?

    • 必须与父类在同一模块或包
    • 必须是 final、sealed 或 non-sealed

留给你的思考题

Records 自动生成 equals()、hashCode() 和 toString() 方法。

但如果你的 Record 包含一个 List 字段,比如 Person(String name, List<String> hobbies),equals() 是如何比较 hobbies 的?

这涉及到浅比较 vs 深比较的问题——默认生成的 Record equals() 是浅比较还是深比较?

基于 VitePress 构建