switch表达式的模式匹配从jdk17开始提出,到jdk20经过4次预览,最终在jdk21成为正式api,增加了null匹配和类型匹配,以及使用when条件的守卫模式,对枚举类型也进行了优化,让switch的功能更强大;
官方说明:JEP 441: Pattern Matching for switch
null加入case判断,不再需要提前判断null了,必要时也可将null跟default放一个case:
@Test
void test() {
String s = null;
switch (s) {
case null -> System.out.println("s=null");
default -> System.out.println("default");
}
Object o = null;
String type = switch(o) {
case String str -> "str";
case null, default -> "null or default";
};
System.out.println(type);
}

case在模式匹配的时候,还可以使用守卫模式when进一步限制条件(只有模式标签才可以使用when):
@Test
void test2() {
Set<Object> objSet = HashSet.newHashSet(5);
objSet.add(null);
objSet.add(123);
objSet.add(5);
objSet.add("abc");
objSet.add("hello world");
objSet.forEach(obj -> {
switch(obj) {
case null -> System.out.println("obj=null");
case Integer i when i > 10 -> System.out.println("int obj > 10 obj=%d".formatted(i));
case Integer i -> System.out.println("int obj=%d".formatted(i));
case String s when s.startsWith("hello") -> System.out.println("string hello* obj=%s".formatted(s));
case String s -> System.out.println("string obj=%s".formatted(s));
default -> System.out.println("default obj=%s class:%s".formatted(obj, obj.getClass()));
}
});
}

swtich表达式对枚举类型也进行了优化,表达式参数是枚举类型的时候,case可以直接使用枚举值也可以带上枚举类名,表达式参数不是枚举类型的时候,比如是枚举继承的父类或者实现的接口时,case里的枚举值必须带枚举类名:(枚举的时候尽量不用default,这样能很快发现swtich缺失的新加的枚举值)
interface I{}
enum E implements I {A,B}
@Test
void test3() {
E e = E.A;
switch(e) { // 参数直接是枚举类型
case A -> System.out.println("E.A"); // 可以直接使用枚举值
case E.B -> System.out.println("E.B"); // 也可以带上枚举类
}
I i = E.B;
switch(i) { // 参数不是枚举类型
case E.A -> System.out.println("E.A"); // 只能使用显式枚举类名
case E.B -> System.out.println("E.B");
// case B -> System.out.println("E.B"); // 直接使用枚举值报错
case I x -> System.out.println("I");
}
}
![]()
一些注意事项:
使用模式匹配或null的case必须要有穷尽性(表达式类型是char, byte, short, int, Character, Byte, Short, Integer, String,enum以外的),case要覆盖所有可能的匹配类型,否则需要用default补充剩余可能的case;
class P{}
class C1 extends P{}
class C2 extends P{}
@Test
void test4() {
P obj = new P();
switch(obj){ // obj有3种可能性:C1、C2、P(不排除还有其他可能)
case C1 c1 -> System.out.println("C1");
case C2 c2 -> System.out.println("C2");
case P p -> System.out.println("P"); // 父类可覆盖所有子类可能性
}
switch(obj) {
case C2 c2 -> System.out.println("_C2");
default -> System.out.println("default"); // default补充了可能是C1或者P
}
}

由于父类可以覆盖子类,所以上面的C1和C2可以注释掉也不会报错:


如果switch表达式里的参数是sealed类型,case可以根据允许的子类穷尽,可以避免default或者父类的不确定性:
sealed interface MySI permits A, B {}
record A(int i) implements MySI {}
record B(String s) implements MySI {}
@Test
void test5() {
MySI mySI = new A(1234);
switch(mySI) { // MySi的permits只有A和B,没有其他可能,caseA和B就穷尽
case A(var i) -> System.out.println("A.i:" + i);
case B b -> System.out.println("B:" + b);
}
}
![]()
另一个需要注意的点是case的条件顺序,前面的case条件不能完全覆盖后面的case条件,否则的后面的条件就永远不会执行(类似try的catch参数);(现代开发工具都会表明这种错误)
相同类型的case通常是先用常量,再用带when的守卫模式,最后用类型本身;
相同范围的case也不能用两次,例如case Object 跟最后的default就可能冲突;


2294

被折叠的 条评论
为什么被折叠?



