1. 经典 switch:过去的限制
回顾一下在引入 pattern matching 之前的 Java switch:老实说,它相当保守。它只能处理原始类型(int、char,后来支持 enum、String),如果你想处理不同的对象类型——就不得不写冗长的 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。
- 如果 obj 是 null,将匹配到专门的分支。
- 其他所有情况走 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,否则无法覆盖所有情况。
GO TO FULL VERSION