总结:Java 17 新特性 - Pattern Matching for switch

本文是对官方文档(https://openjdk.org/jeps/406)的概括总结,更详细的内容请看官方文档的不完全翻译(https://blog.csdn.net/weixin_38833041/article/details/125450755

简介

在Java 17 preview中,switch支持模式匹配,并且允许选择器表达式的值为null。模式分为两种,分别是保护模式(guarded patterns)和带括号的模式(parenthesized patterns),其中保护模式允许使用任意的布尔表达式细化模式匹配的逻辑,带括号的模式用于解决歧义问题。

概述 - Java 17 中 switch 模式匹配新特性

  1. 增强的类型检查

    1. switch支持更多的类型:整型原始类型或任何引用类型
    2. 子类型的case必须出现在父类型case的前面
  2. switch 表达式和语句的完整性

    1. 使用模式或null标签或其选择器表达式不是下面这些类型之一(char、byte、short、int、Character、Byte、Short、Integer、String 或 enum 类型)的 switch 语句需要保证完整性。
  3. 模式变量声明的范围

    switch 的 case 标签的模式变量声明的范围包括:

    • 出现在箭头右侧的表达式、块或 throw 语句。
    • 出现在冒号右侧的语句组的块语句。
  4. 处理null

    1. 如果选择器表达式的计算结果为 null,如果能匹配某个case,则执行相应的代码;如果没有匹配的case,则像以前一样抛出 NullPointerException。
    2. case null 可以与其他模式或default组合使用
  5. 保护模式(guarded patterns)和括号模式(parenthesized patterns

    1. 保护模式允许使用任意的布尔表达式细化模式匹配的逻辑
    2. 括号模式用于解决歧义问题。

详解

首先了解Java 17 中的switch与其他版本的区别

与Java 14的switch的区别

Java 14中:switch只能用于几种类型的值——数字类型、枚举类型和字符串,而且只能测试常量是否完全相等。

Java 17中:switch语句和表达式支持任何类型,case不仅支持常量,还支持模式(保护模式和括号模式)。

Java 16 中的 switch 块支持两种样式

一种基于标记的语句组(: 形式),其中可以fallthrough(没遇到break之前可以继续执行下一个case),另一种基于单一后项(single-consequent)形式(-> 形式),其中不可以fallthrough。在前一种风格中,多个标签通常写成 case l1: case l2: 而在后一种风格中,多个标签写成 case l1, l2:。

下面解释上面提到的新特性

1、增强类型检查

选择器表达式类型

普通switch的选择器表达式的类型必须是整型原始类型(char、byte、short 或 int)、相应的封装类型(Character、Byte、Short 或 Integer)、String 或枚举类型。java 17对此进行了扩展,并要求选择器表达式的类型是整型原始类型或任何引用类型。

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("Color with " + Color.values().length + " values");
        case Point p  -> System.out.println("Record class: " + p.toString());
        case int[] ia -> System.out.println("Array of ints of length" + ia.length);
        default       -> System.out.println("Something else");
    }
}

模式标签的主导地位

如果 switch 块仅包含类型模式的case标签,子类型的case必须出现在父类型case的前面。如下错误的代码:CharSequence 是String 的父接口,所以case String s 必须在 case CharSequence cs 前面。

static void error(Object o) {
    switch(o) {
        case CharSequence cs ->
            System.out.println("A sequence of length " + cs.length());
        case String s ->    // Error - pattern is dominated by previous pattern
            System.out.println("A string: " + s);
        default -> {
            break;
        }
    }
}

2、switch 表达式和语句中模式标签的完整性

case必须覆盖选择器表达式的所有可能值。例如:前两个case覆盖了String和Integer类型,default覆盖了剩余所有类型,如果没有default会发生编译错误:‘switch’ statement does not cover all possible input values

static int coverage(Object o) {
    return switch (o) {
        case String s  -> s.length();
        case Integer i -> i;
        default -> 0;
    };
}

但是如果一个switch块有多个全匹配(total pattern)标签(例如 case Object 和 default同时出现),这也是一个编译时错误。

再看一个密封类的例子:由于选择器表达式的类型 S 是一个密封接口,其允许的子类恰好是 A、B 和 C,所以这个switch块是完整的。因此,不需要default标签。

sealed interface S permits A, B, C {}
final class A implements S {}
final class B implements S {}
record C(int i) implements S {}  // Implicitly final

static int testSealedCoverage(S s) {
    return switch (s) {
        case A a -> 1;
        case B b -> 2;
        case C c -> 3;
    };
}

使用模式或null标签或其选择器表达式不是下面这些类型之一(char、byte、short、int、Character、Byte、Short、Integer、String 或 enum 类型)的 switch 语句需要保证完整性。

3、模式变量声明的作用域

  • switch 的 case 标签的模式变量声明的范围包括:出现在箭头右侧的表达式、块或 throw 语句。
  • switch 的 case 标签的模式变量声明的范围包括:出现在冒号右侧的语句组的块语句。

第一条规则的代码如下:模式变量 c 的声明范围是第一个箭头右侧的块。模式变量 i 的声明范围是第二个箭头右侧的 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;
        }
    }
}

第二条规则的代码如下:模式变量c的声明范围包括语句组(该case标签中)的所有语句,即两个if语句和println语句。c的范围不包括default语句组的语句,即使执行完第一个语句组后会继续执行default标签中的语句。

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();
    }
}

需要注意的是,冒号形式可能导致“Illegal fall-through to a pattern”编译时异常,即有可能进入下一个case标签的模式变量的声明会导致编译错误。如下代码,如果 o 的值是一个Character ,那么 switch 块的执行可能会进入 case Integer i,其中模式变量 i 不会被初始化。(在case Character c:最后一行加入break就不报错了)

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");
        case Integer i:                 // Compile-time error
            System.out.println("An integer " + i);
        default:
            break;
    }
}

4、处理null

null的匹配规则

如果选择器表达式的值为空,以前的switch立即抛出 NullPointerException 。但在Java 17 中会根据case确定switch的行为:

  • 如果选择器表达式的计算结果为 null,则任何 case null 标签或总模式标签(total pattern)都被认为是匹配的。如果没有与 switch 块相关联的标签,则 switch 会像以前一样抛出 NullPointerException。注意default不匹配null。
  • 如果选择器表达式的计算结果为非空值,那么我们会像往常一样选择匹配的 case 标签。如果没有 case 标签匹配,则任何全匹配(match-all label)标签(比如default)都被认为匹配。

对于第一条规则,参考下面的代码:当o为null时,输出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");
    }
}

当o为null时,下面的代码输出Something else。没有case null不一定会抛异常,只要有匹配的case就不会抛异常。

static void test(Object o) {
        switch (o) {
            case String s -> System.out.println("String");
            case Object ob       -> System.out.println("Something else");
        }
    }

当o为null时,因为没有与 switch 块相关联的标签,所有 switch 会像以前一样抛出 NullPointerException。

static void test(Object o) {
    switch (o) {
        case String s -> System.out.println("String");
        default       -> System.out.println("Something else");
    }
}

4b.null与其他模式的组合形式

例如null与String组合,冒号形式和箭头形式都支持

Object o = ...
switch(o) {
    case null: case String s: 
        System.out.println("String, including null");
        break;
    ...
}

例如null与default组合,冒号形式和箭头形式都支持

Object o = ...
switch(o) {
    ...
    case null, default ->
        System.out.println("The rest (including null)");
}

5、保护模式和括号模式(Guarded and parenthesized patterns)

保护模式

保护模式的形式为 p && e,其中 p 是模式,e 是布尔表达式。在受保护模式中,任何使用但未在子表达式中声明的局部变量、形式参数或异常参数必须是最终(final)的或有效的最终的(effectively final:该变量未使用final修饰,但是其值在初始化后永远不会改变)。

p 中的任何模式变量声明的范围都包括表达式 e。这允许诸如 String s && (s.length() > 1) 之类的模式,它匹配可以强制转换为 String 的值,使得字符串的长度大于 1。

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

括号模式

括号模式的形式为 §,其中 p 是模式。带括号的模式 § 引入了由子模式 p 引入的模式变量。如果一个值与模式 p 匹配,则它与带括号的模式 § 匹配。

如表达式 e instanceof String s && s.length() > 1 会解析为表达式 (e instanceof String s ) && (s.length() > 1)。如果 想让&& 成为受保护模式的一部分,则整个模式应该用括号括起来,例如 e instanceof (String s && s.length() > 1)。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值