跳至主要內容

Java 17 新特性:switch模式匹配(Preview)

会敲代码的程序猿原创JavaJava Features大约 4 分钟

Java 17 新特性:switch模式匹配(Preview)

当case标签可以有模式时,有如下四个主要的设计问题,我们一一来看:

  1. 增强类型检查
  2. switch表达式和语句的完整性
  3. 模式变量声明的作用域
  4. 处理null

模式匹配设计

增强类型检查

通过扩展switch模式匹配的case标签,现在支持除了原始数据类型charbyteshortint)之外, 相应的包装类CharacterByteShortInteger)、String 以及Enum类型等任何引用类型

record Point(int i, int j) {}
enum Color { RED, GREEN, BLUE; }

static void typeTester(Object o) {
    switch (o) {
        case null     -> System.out.println("null");
        case String s -> System.out.println("String");
        case Color c  -> System.out.println("Enum,颜色具有 " + Color.values().length + " 个值");
        case Point p  -> System.out.println("Record Class: " + p.toString());
        case int[] ia -> System.out.println("Array,长度为" + ia.length);
        default       -> System.out.println("其他情况");
    }
}

注意⚠️:要避免模式标签支配(编译异常)

如果一个模式标签在switch块中被先前的模式标签支配, 或者存在多个全匹配的标签(default 和 total类型模式), 则会产生编译时错误。

  • 例1: 模式 case CharSequence cs 支配 case String s ,因为 String 是 CharSequence 的子类
  • 例2: 总模式的情况,如 case p 支配 case null 模式,因为总模式匹配所有值,包括null
  • 例3: 模式 case p 支配 case p && e ,因为满足第一个模式的值也满足第二个模式
  • 例4: 模式 case String s 支配了带条件的模式 case String s && s.length() > 0
switch(o) {
    case CharSequence cs ->
        System.out.println("一个长度为" + cs.length() + "的序列");
    case String s ->    // 编译错误 - 模式被前一个模式支配
        System.out.println("一个字符串:" + s);
    default -> {
        break;
    }
}

switch表达式和语句的完整性

通常情况下,通过添加default标签,可以确保switch块的完整性。

static void printType(Object o) {
    switch (o) {
        case String s -> System.out.println("String");
        case Integer i -> System.out.println("Integer");
        default -> System.out.println("Other");
    }
}

如果switch表达式的类型是密封类(JEP 409open in new window), 则类型覆盖检查会考虑密封类的permits子句,以确保switch块的完整性。

以下是一个密封接口Animal的示例,包括Dog和Cat两个允许的子类:

sealed interface Animal permits Dog, Cat {}
class Dog implements Animal {}
class Cat implements Animal {}

static String getSound(Animal animal) {
    return switch (animal) {
        case Dog d -> "Woof!";
        case Cat c -> "Meow!";
        // no default needed!
    };
}

在这种情况下,由于编译器知道只有Dog和Cat是可能的类型,所以可以不需要default标签。 同样,对于枚举类,每个常量都有一个子句,也不需要default标签。

模式变量声明的作用域

instanceof(JEP 394open in new window)进行模式匹配模式变量的作用域限定在匹配的条件表达式和相应的then块中。 如果匹配失败,模式变量在else块中不可见。

static void test(Object o) {
    if ((o instanceof String s) && s.length() > 3) {
        System.out.println(s);
    } else {
        System.out.println("Not a string");
    }
}

switch语句的case标签进行模式匹配,有以下两条规则:

  1. ->形式:作用域包括箭头右侧的表达式、块或 throw 语句

    static void test(Object o) {
        switch (o) {
            case Character c -> {
                if (c.charValue() == 7) {
                    System.out.println("Ding!");
                }
                System.out.println("Character");
            }
            case Integer i ->
                throw new IllegalStateException("Invalid Integer argument of value " + i.intValue());
            default -> {
                break;
            }
        }
    }
    
  2. :形式,则其作用域包括语句组的块语句,直到遇到下一个switch标签或其他控制流语句

    static void test(Object o) {
        switch (o) {
            case Character c:
                if (c.charValue() == 7) {
                    System.out.print("Ding ");
                }
                if (c.charValue() == 9) {
                    System.out.print("Tab ");
                }
                System.out.println("character");
            default:
                System.out.println();
        }
    }
    

处理null

引入新的null标签,用于明确处理选择表达式为null的情况

// test(null) 不再抛出NullPointerException,而是打印 "null!"
static void test(Object o) {
    switch (o) {
        case null     -> System.out.println("null!");
        case String s -> System.out.println("String");
        default       -> System.out.println("Something else");
    }
}

由空标签产生的新标签形式, JDK 16中,switch块支持两种风格,

  1. : 形式,允许fallthrough,多个标签通常写为case l1: case l2:
  2. ->形式,不允许fallthrough,多个标签写为case l1, l2->
// 处理 null 和 String 标签,使用 : 形式
switch (o) {
   case null: case String s:
       System.out.println("String, including null");
       break;
   // 更多的 cases...

}

// 结合 null case 和 default 标签,使用 -> 形式
switch (o) {
   // 更多的 cases...
   case null, default ->
       System.out.println("The rest (including null)");
}

保护模式和括号模式

为了增强代码的可读性并避免歧义,引入了两种新的模式匹配技术:

  • 保护模式 (guarded patterns),允许在模式匹配成功后添加一个布尔表达式
  • 括号模式 (parenthesized patterns),将模式放在括号中,避免歧义,控制顺序

在成功匹配模式后,我们经常会进一步测试匹配结果。这会导致代码变得繁琐,例如:

static void test(Object o) {
    switch (o) {
    case String s:
        if (s.length() == 1) { ... }
        else { ... }
        break;
    ...
    }
}

使用保护模式,写成p && e改进上面的代码,使其更加简洁

static void test(Object o) {
    switch (o) {
        case String s && s.length() == 1 -> ...
        case String s                    -> ...
    }
}

JDK 17中还加入了括号模式,以避免解析歧义。支持括号内写入(p) 其中p是一个模式。在JDK 21中,括号模式被移除。

启用预览功能

Preview阶段的功能并不是默认开启的,需要在编译和运行时启用。

java --enable-preview --source 17 PatternMatching.java