CodeGym /课程 /JAVA 25 SELF /在 switch 中进行 Pattern Matching(Java 17/21+)

在 switch 中进行 Pattern Matching(Java 17/21+)

JAVA 25 SELF
第 65 级 , 课程 2
可用

1. 经典 switch:过去的限制

回顾一下在引入 pattern matching 之前的 Java switch:老实说,它相当保守。它只能处理原始类型(intchar,后来支持 enumString),如果你想处理不同的对象类型——就不得不写冗长的 if-else 链。

Object obj = ...;
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println("字符串:" + s);
} else if (obj instanceof Integer) {
    Integer i = (Integer) obj;
    System.out.println("整数:" + i);
} else if (obj == null) {
    System.out.println("null!");
} else {
    System.out.println("其他内容");
}

方便吗?并不。代码臃肿,容易出错,而且一旦加入新类型——又得重写整条 if-else 链。

2. 在 switch 中进行 Pattern Matching:Java 17/21+ 的变革

Java 17(预览)并在 Java 21+ 中最终稳定地引入了支持模式匹配(pattern matching)的新 switch。现在 switch 不仅能处理原始类型,还能处理任意对象、类型,甚至 null,并支持额外条件(guard 表达式)。

新语法:简洁而强大

switch 中进行简单模式匹配的示例:

Object obj = ...;
switch (obj) {
    case String s -> System.out.println("字符串:" + s);
    case Integer i -> System.out.println("整数:" + i);
    case null -> System.out.println("null!");
    default -> System.out.println("其他内容");
}

这里发生了什么?

  • 如果 obj 是字符串,变量 s 会自动被视为 String 类型。
  • 如果 obj 是整数,变量 i 就是 Integer
  • 如果 objnull,将匹配到专门的分支。
  • 其他所有情况走 default

为什么更方便?

  • 无需手动类型转换: Java 会帮你确保类型安全。
  • 更少代码: 不再需要多余的括号、强转和重复。
  • 更安全: 编译器会检查你是否遗漏了可能的分支(特别是配合 sealed 类,下面会讲)。
  • 易于扩展新类型: 只需再添加一个 case 分支。

3. 在 switch 中进行 Pattern Matching:基础示例

示例 1:处理不同类型

Object obj = 42;

switch (obj) {
    case String s -> System.out.println("字符串:" + s);
    case Integer i -> System.out.println("整数:" + i);
    case null -> System.out.println("这是 null");
    default -> System.out.println("未知类型");
}

输出:

整数:42

示例 2:使用 guard 表达式(when)

有时仅知道类型还不够——我们还希望添加额外条件。

Object obj = "Hello, world!";

switch (obj) {
    case String s when s.length() > 10 -> System.out.println("长字符串:" + s);
    case String s -> System.out.println("短字符串:" + s);
    case Integer i when i > 0 -> System.out.println("正整数:" + i);
    case Integer i -> System.out.println("非正整数:" + i);
    default -> System.out.println("其他内容");
}

输出:

长字符串:Hello, world!

请注意:

  • when 是 guard 表达式,即给 case 增加的附加判断。
  • 如果条件不通过,会继续检查后续的 case

示例 3:处理 null

Object obj = null;

switch (obj) {
    case null -> System.out.println("这是 null!");
    default -> System.out.println("不是 null");
}

输出:

这是 null!

4. 在 switch 中配合类与继承的 Pattern Matching

在类层级结构中,switch 的模式匹配尤其好用。

sealed interface Shape permits Circle, Rectangle, Square {}

final class Circle implements Shape {
    final double radius;
    Circle(double radius) { this.radius = radius; }
}

final class Rectangle implements Shape {
    final double width, height;
    Rectangle(double width, double height) { this.width = width; this.height = height; }
}

final class Square implements Shape {
    final double side;
    Square(double side) { this.side = side; }
}
Shape shape = new Rectangle(2, 3);

String description = switch (shape) {
    case Circle c -> "半径 " + c.radius + " 的圆";
    case Rectangle r -> "矩形 " + r.width + "x" + r.height;
    case Square s -> "边长为 " + s.side + " 的正方形";
};
System.out.println(description);

输出:

矩形 2.0x3.0

要点: 如果你使用了 sealed 类并处理了其所有变体,就不需要 default 分支——编译器知道不存在其他可能!

5. 在 switch 中使用 record 类与嵌套模式

switch 中,模式匹配也支持 record 类与嵌套模式(自 Java 21+ 起)。

record Point(int x, int y) {}
record Line(Point start, Point end) {}

Object obj = new Line(new Point(1, 2), new Point(3, 4));

String result = switch (obj) {
    case Line(Point(int x1, int y1), Point(int x2, int y2)) ->
        "从 (" + x1 + "," + y1 + ") 到 (" + x2 + "," + y2 + ") 的线";
    case Point(int x, int y) -> "点 (" + x + "," + y + ")";
    default -> "未知对象";
};
System.out.println(result);

输出:

从 (1,2) 到 (3,4) 的线

6. 在 switch 中进行 pattern matching 的特性与限制

变量仅在对应的 case 内可见

在模式中声明的变量(例如 String s)只在相应的 case 代码块内可用,在其外部不可见。

switch (obj) {
    case String s -> System.out.println(s); // 这里能看到 s
    // System.out.println(s); // 这里是编译错误!
}

处理 null

  • 在旧的对象 switch 中,传入 null 会导致 NullPointerException
  • 在新的模式匹配 switch 中,可以通过 case null 显式处理 null

Guard 表达式(when)

附加条件(when)仅用于表达式风格的 switch(箭头语法 ->)。

case String s when s.isEmpty() -> System.out.println("空字符串");

对 JDK 与 IDE 的要求

  • switch 中的模式匹配是 Java 17(预览)与 Java 21+(稳定)引入的特性。
  • 需要 JDK 21 以及支持这些新特性的 IDE(例如 IntelliJ IDEA 2023+)。

7. 在实际应用中使用 pattern matching switch

来看一个教学用应用——简单的任务跟踪器。假设你有一个抽象的任务类型以及多种具体任务:

sealed interface Task permits SimpleTask, DeadlineTask, MeetingTask {}

final class SimpleTask implements Task {
    final String description;
    SimpleTask(String description) { this.description = description; }
}

final class DeadlineTask implements Task {
    final String description;
    final java.time.LocalDate deadline;
    DeadlineTask(String description, java.time.LocalDate deadline) {
        this.description = description;
        this.deadline = deadline;
    }
}

final class MeetingTask implements Task {
    final String topic;
    final java.time.LocalTime time;
    MeetingTask(String topic, java.time.LocalTime time) {
        this.topic = topic;
        this.time = time;
    }
}

将逻辑浓缩在一个紧凑的 switch 中:

Task task = new DeadlineTask("提交项目", java.time.LocalDate.of(2024, 7, 1));

String info = switch (task) {
    case SimpleTask st -> "普通任务:" + st.description;
    case DeadlineTask dt -> "带截止日期的任务:" + dt.description + ",截止至 " + dt.deadline;
    case MeetingTask mt -> "会议:" + mt.topic + " 于 " + mt.time;
};
System.out.println(info);

输出:

带截止日期的任务:提交项目,截止至 2024-07-01

8. 使用 pattern matching switch 的常见错误

错误 1:忘记处理 null。 如果未添加 case null,而 obj 可能为 null——就会得到 MatchException(或在旧 switch 中得到 NullPointerException)。尽可能总是显式处理 null

错误 2:未覆盖 sealed 类的所有子类。 如果你使用了 sealed 类却没有在 switch 中处理所有变体,编译器会报错或要求添加 default 分支。扩展层级时别忘了添加新的 case

错误 3:pattern 变量在 case 外不可用。 在模式中声明的变量只存在于箭头分支内部,试图在其外使用会导致编译错误。

错误 4:在旧版 JDK 或旧版 IDE 中使用新特性。switch 中使用模式匹配需要 Java 21+ 以及 IDE 的相应支持。如果使用旧版本——会出现编译错误或不正确的提示。

错误 5:对 guard 表达式的误用。 想在旧式块状 switch 中使用 when?不行。守卫只在箭头语法 -> 的表达式风格中可用。

错误 6:在开放层级中忘记了 default 分支。 如果你的 switch 并不只处理 sealed 类,那么务必添加 default,否则无法覆盖所有情况。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION