JDK 17 switch模式匹配

5,936 阅读4分钟

这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战

欢迎关注公众号OpenCoder,来和我做朋友吧~❤😘😁🐱‍🐉👀

简介

Pattern Matching for switch(Preview)

在switch中使用模式匹配,预览版本。

预览版本

  • 有可能在之后的版本删除
  • 有可能计划进一步增强

在 Java 16 中, JEP 394 扩展了 instanceof 运算符以采用类型模式并执行模式匹配。

static String formatter(Object o){
    String formatted = "unknown";
    if (o instanceof Integer i) {
        formatted = String.format("int %d", i);
    } else if (o instanceof Long l) {
        formatted = String.format("long %d", l);
    } else if (o instanceof Double d) {
        formatted = String.format("double %f", d);
    } else if (o instanceof String s) {
        formatted = String.format("String %s", s);
    }
    return formatted;
}

在Java 17中, JEP406

switch 是模式匹配的完美匹配!如果我们扩展 switch 语句和表达式以适用于任何类型,并允许使用模式而不是常量进行 case 标签,那么我们可以更清晰可靠地重写上述代码

static String formatterPatternSwitch(Object o) {
    return switch (o) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> o.toString();
    };
}
  • 好处

    • 语义更清晰:“参数 o 最多匹配以下条件之一”

    • 在这种情况下,我们更有可能在 O(1) 时间内执行调度(==效率高==)。我们用断点调试这两段代码

      • if...else

        逐行校验

      • switch

        直接到default

  • switch模式匹配的目标

    • 通过允许模式出现在 case 中,扩展 switch 表达式和语句的表现力和适用性。
    • 允许switch的case使用null
    • 引入两种新的模式:保护模式,允许使用任意布尔表达式来改进模式匹配逻辑,以及带括号的模式,以解决一些解析歧义。
    • 确保所有现有的 switch 表达式和语句在没有更改的情况下继续编译并以相同的语义执行。
    • 确保和旧版本switch表达式和语句的兼容性

语法

增强 switch 语句和表达式两种方式

  • 扩展case语句中除常量外,还可以使用模式匹配
  • 除了案例中的模式,还有两种新的模式:保护模式和括号模式。

对于模式匹配有四个特点

  • 增强的类型检查:选择器表达式的类型包括:基本类型或任何引用类型(包括null)。

    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 表达式和语句的完整性

    • 存在子父类关系
    // 错误
    static void error(Object o) {
        switch(o) {
            case CharSequence cs ->
                System.out.println("A sequence of length " + cs.length());
            case String s ->    // 编译错误 - CharSequence是String的父类,提示该模式已经由前一个模式匹配
                System.out.println("A string: " + s);
            default -> {
                break;
            }
        }
    }
    
    // 正确
    static void error(Object o) {
        switch(o) {
            case String s -> System.out.println("A string: " + s);
            case CharSequence cs -> System.out.println("A sequence of length " + cs.length());
            default -> {
                break;
            }
        }
    }
    
    • 缺少default
    // 错误
    static int coverage(Object o) {
        return switch (o) {         // 编译错误,类型匹配不完整,缺少default
            case String s  -> s.length();
            case Integer i -> i;
        };
    }
    // 正确
    static int coverage(Object o) {
        return switch (o) {
            case String s  -> s.length();
            case Integer i -> i;
            default -> 0;
        };
    }
    
    • 类型匹配检查

      sealed是密封类的语法,我们会在文章后续进行探讨。

      表示可以实现接口S的类有 A,B,C

    sealed interface S permits A, B, C {}
    final class A implements S {}
    final class B implements S {}
    final class C implements S {} 
    
    // 错误
    static void switchStatementComplete(S s) {
        switch (s) {    // 编译错误,缺少类型B
            case A a :
                System.out.println("A");
                break;
            case C c :
                System.out.println("C");
                break;
        };
    }
    // 正确
    static int testSealedCoverage(S s) {
        return switch (s) {
            case A a -> 1;
            case B b -> 2;
            case C c -> 3;
        };
    }
    
  • 模式变量声明的范围

    • case语句后箭头右侧可以出现的内容:
      • 表达式
      • 代码块
      • 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语句
            throw new IllegalStateException("Invalid Integer argument of value " + i.intValue());
        default -> {
            break;
        }
    }
}
  • 保护模式和括号模式。
    • 保护模式:我们可以添加一种称为保护模式的新模式,写作 【表达式1】 && 【表达式2】,它允许通过任意布尔表达式对模式进行细化。
    • 括号模式:给任意布尔表达式加上括号,以避免解析歧义
class Shape {}
class Rectangle extends Shape {}
class Triangle  extends Shape { int calculateArea() { ... } }

static void testTriangle(Shape s) {
    switch (s) {
        case null:
            break;
        case Triangle t:
            if (t.calculateArea() > 100) {
                System.out.println("Large triangle");
                break;
            }
        default:
            System.out.println("A shape, possibly a small triangle");
    }
}
// 优化
static void testTriangle(Shape s) {
    switch (s) {
        case Triangle t && (t.calculateArea() > 100) -> // 括号模式
            System.out.println("Large triangle");
        case Triangle t ->							
            System.out.println("Small triangle");
        default ->
            System.out.println("Non-triangle");
    }
}
  • 处理null值
    • 在case中可以使用null
    • 以及null引出的新的模式:case 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");
    }
}

static void test(Object o) {
    switch (o) {
        case null,default     -> // 
            System.out.println("Something else (包括null)");
    }
}

参考资源

下期预告

下期我们将一起学习探讨Sealed Classes密封类