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 仍然除外)。