switch 中 case 语句的顺序很重要。如果基类先出现,就会支配任何出现在后面的 case:
Dominance.java JDK 17
sealed interface Base {
}
record Derived() implements Base {
}
public class Dominance {
static String test(Base base) {
return switch (base) {
case Derived d -> "Derived";
case Base b -> "B"; // [1]
};
}
}
基类 Base 处于最后一个位置,即行 [1]。但是如果将该行上移,便会出现在 case Derived 的前面,这就意味着 switch 将永远没有机会测试到 Derived 了,因为任何子类都将被 case Base 捕获。如果尝试这么做,编译器就会报错,“该case标签被前面的 case 标签支配了”(this case label is dominated by a preceding case label)。
在使用守卫时,顺序的敏感性常常会体现出来。在 Tanks.java 中,将 switch 里处于末尾的 case 移动到更高的位置会导致“支配性”的错误消息。如果在同一个模式上有多个守卫,更具体的模式必须出现在更泛化的模式之前,否则更泛化的模式会在更具体的模式之前进行匹配,后者就永远没有机会被检查了。所幸编译器会报告支配性的问题。
编译器只有在一个模式中的类型支配了另一个模式中的类型时,才能检测出支配性问题。它无法知道守卫中的逻辑是否会导致问题:
People.java JDK 17
import java.util.List;
record Person(String name, int age) {
}
public class People {
static String categorize(Person person) {
return switch (person) {
case Person p && p.age() > 40 // [1]
-> p + " is middle aged";
case Person p &&
(p.name().contains("D") || p.age() == 14) -> p + " D or 14";
case Person p && !(p.age() >= 100) // [2]
-> p + " is not a centenarian";
case Person p -> p + " Everyone else";
};
}
public static void main(String[] args) {
List.of(
new Person("Dorothy", 15),
new Person("John Bigboote", 42),
new Person("Morty", 14),
new Person("Morty Jr.", 1),
new Person("Jose", 39),
new Person("Kane", 118)
).forEach(
p -> System.out.println(categorize(p))
);
}
}
运行结果如下:
模式 [2] 中的守卫似乎可以匹配到 118 岁的 Kane,但是 Kane 被模式 [1] 匹配到了。不能依赖编译器来帮你处理守卫表达式的逻辑。
如果没有最后的 case Person p,编译器会认为“switch 表达式没有覆盖所有可能的输入”。而有了 case Person p,也仍然不需要 default。因此最泛化的 case 便成了 default。由于 switch 的参数是 Person,因此所有的 case 都被覆盖了(null 仍然除外)。