Pattern Matching for Switch – 开关的模式匹配

最后修改: 2021年 10月 21日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

The Java SE 17 release introduces pattern matching for switch expressions and statements (JEP 406) as a preview feature. Pattern matching provides us more flexibility when defining conditions for switch cases.

Java SE 17 版本为 switch 表达式和语句引入了模式匹配(JEP 406),作为一项预览功能。模式匹配在为switch情况定义条件时为我们提供了更大的灵活性

In addition to case labels that can now contain patterns, the selector expression is no longer limited to just a few types. Before pattern matching, switch cases supported only simple testing of a selector expression that needs to match a constant value exactly.

除了现在可以包含模式的案例标签外,选择器表达式也不再只限于几种类型。在模式匹配之前,switch案例只支持对需要精确匹配一个常量值的选择器表达式进行简单的测试。

In this tutorial, we will cover three different pattern types that can be applied in switch statements. We’ll also explore some switch specifics, like covering all values, ordering subclasses, and handling null values.

在本教程中,我们将介绍可应用于switch语句的三种不同模式类型。我们还将探讨一些switch的具体内容,如覆盖所有值、排序子类和处理空值。

2. Switch Statement

2.开关声明

We use switch in Java to transfer control to one of the several predefined case statements. Which statement gets selected depends on the value of the switch selector expression.

我们在Java中使用switch 来将控制权转移到几个预定义的案例语句中的一个。哪个语句被选中取决于switch选择器表达式的值。

In the earlier versions of Java, the selector expression had to be a number, a string, or a constant. Also, the case labels could only contain constants:

在早期的Java版本中,选择器表达式必须是一个数字、一个字符串或一个常数。而且,案例标签只能包含常数。

final String b = "B";
switch (args[0]) {
    case "A" -> System.out.println("Parameter is A");
    case b -> System.out.println("Parameter is b");
    default -> System.out.println("Parameter is unknown");
};

In our example, if variable b wasn’t final, the compiler would throw a constant expression required error.

在我们的例子中,如果变量b不是final,编译器会抛出一个常量表达式需要的错误。

3. Pattern Matching

3.模式匹配

Pattern matching, in general, was first introduced as a preview feature in Java SE 14.

一般来说,模式匹配是在Java SE 14中首次作为预览功能引入的。

It was limited to only one form of a pattern – the type pattern. A typical pattern consists of a type name and the variable to bind the result to.

它只限于模式的一种形式–类型模式。一个典型的模式由一个类型名称和要绑定结果的变量组成。

Applying type patterns to the instanceof operator simplifies type checking and casting. Moreover, it enables us to combine both into a single expression:

将类型模式应用于instanceofoperator简化了类型检查和铸造。此外,它使我们能够将两者结合到一个单一的表达式中。

if (o instanceof String s) {
    System.out.printf("Object is a string %s", s);
} else if (o instanceof Number n) {
    System.out.printf("Object is a number %n", n);
}

This built-in language enhancement helps us write less code with enhanced readability.

这种内置的语言增强功能有助于我们编写更少的代码,并增强可读性。

4. Patterns for Switch

4.开关的模式

Pattern matching for instanceof became a permanent feature in Java SE 16.

针对 instanceof的模式匹配在Java SE 16中成为一项永久性功能。

With Java 17, the application of pattern matching now also expands to switch expressions.

在Java 17中,模式匹配的应用现在也扩展到switch表达式

However, it is still a preview feature, so we need to enable preview to use it:

然而,它仍然是一个预览功能,所以我们需要启用预览功能来使用它。

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

4.1. Type Pattern

4.1.类型模式

Let’s look at how type patterns and the instanceof operator can be applied in switch statements.

让我们看看类型模式和instanceof操作符如何在switch语句中应用。

As an example, we’ll create a method that converts different types to double using if-else statements. Our method will simply return zero if the type is not supported:

作为一个例子,我们将创建一个方法,使用if-else语句将不同的类型转换为double。如果该类型不被支持,我们的方法将简单地返回0。

static double getDoubleUsingIf(Object o) {
    double result;
    if (o instanceof Integer) {
        result = ((Integer) o).doubleValue();
    } else if (o instanceof Float) {
        result = ((Float) o).doubleValue();
    } else if (o instanceof String) {
        result = Double.parseDouble(((String) o));
    } else {
        result = 0d;
    }
    return result;
}

We can solve the same problem with less code using type patterns in switch:

我们可以使用switch中的类型模式以更少的代码解决同样的问题。

static double getDoubleUsingSwitch(Object o) {
    return switch (o) {
        case Integer i -> i.doubleValue();
        case Float f -> f.doubleValue();
        case String s -> Double.parseDouble(s);
        default -> 0d;
    };
}

In earlier versions of Java, the selector expression was limited to only a few types. However, with type patterns, the switch selector expression can be of any type.

在早期的Java版本中,选择器表达式只限于几种类型。然而,通过类型模式,switch选择器表达式可以是任何类型。

4.2. Guarded Pattern

4.2.守护的模式

Type patterns help us transfer control based on a particular type. However, sometimes, we also need to perform additional checks on the passed value.

类型模式帮助我们根据特定的类型来传输控制。然而,有时候,我们也需要对传递的值进行额外的检查。

For example, we may use an if statement to check the length of a String:

例如,我们可以使用一个if语句来检查一个String的长度。

static double getDoubleValueUsingIf(Object o) {
    return switch (o) {
        case String s -> {
            if (s.length() > 0) {
                yield Double.parseDouble(s);
            } else {
                yield 0d;
            }
        }
        default -> 0d;
    };
}

We can solve the same problem using guarded patterns. They use a combination of a pattern and a boolean expression:

我们可以用有保护的模式来解决同样的问题。它们使用模式和布尔表达式的组合。

static double getDoubleValueUsingGuardedPatterns(Object o) {
    return switch (o) {
        case String s && s.length() > 0 -> Double.parseDouble(s);
        default -> 0d;
    };
}

Guarded patterns enable us to avoid additional if conditions in switch statements. Instead, we can move our conditional logic to the case label.

保护模式使我们能够避免在switch语句中附加if条件。相反,我们可以将我们的条件逻辑移到case标签中

4.3. Parenthesized Pattern

4.3.括号内的图案

In addition to having conditional logic in the cases label, parenthesized patterns enable us to group them.

除了在案例标签中具有条件逻辑外,母题模式使我们能够对它们进行分组

We can simply use parentheses in our boolean expressions when performing additional checks:

在进行额外的检查时,我们可以简单地在布尔表达式中使用圆括号。

static double getDoubleValueUsingParenthesizedPatterns(Object o) {
    return switch (o) {
        case String s && s.length() > 0 && !(s.contains("#") || s.contains("@")) -> Double.parseDouble(s);
        default -> 0d;
    };
}

By using parentheses, we can avoid having additional if-else statements.

通过使用括号,我们可以避免出现额外的 if-else语句。

5. Switch Specifics

5.开关的具体内容

Let’s now look at a couple of specific cases to consider while using pattern matching in switch.

现在让我们看看在switch中使用模式匹配时需要考虑的几个具体案例。

5.1. Covering All Values

5.1.涵盖所有价值

When using pattern matching in switch, the Java compiler will check the type coverage.

switch中使用模式匹配时,Java编译器将检查类型覆盖率

Let’s consider an example switch condition accepting any object but covering only the String case:

让我们考虑一个接受任何对象的switch条件的例子,但只包括String情况。

static double getDoubleUsingSwitch(Object o) {
    return switch (o) {
        case String s -> Double.parseDouble(s);
    };
}

Our example will result in the following compilation error:

我们的例子将导致以下编译错误。

[ERROR] Failed to execute goal ... on project core-java-17: Compilation failure
[ERROR] /D:/Projects/.../HandlingNullValuesUnitTest.java:[10,16] the switch expression does not cover all possible input values

This is because the switch case labels are required to include the type of the selector expression.

这是因为 switch case标签需要包括选择器表达式的类型

The default case label may also be applied instead of a specific selector type.

default案例标签也可以被应用,而不是特定的选择器类型。

5.2. Ordering Subclasses

5.2.对子类进行排序

When using subclasses with pattern matching in switch, the order of the cases matters.

当在switch中使用模式匹配的子类时,案例的顺序很重要

Let’s consider an example where the String case comes after the CharSequence case.

让我们考虑一个例子,String情况在CharSequence情况之后。

static double getDoubleUsingSwitch(Object o) {
    return switch (o) {
        case CharSequence c -> Double.parseDouble(c.toString());
        case String s -> Double.parseDouble(s);
        default -> 0d;
    };
}

Since String is a subclass of CharSequence, our example will result in the following compilation error:

由于StringCharSequence的子类,我们的例子将导致以下编译错误。

[ERROR] Failed to execute goal ... on project core-java-17: Compilation failure
[ERROR] /D:/Projects/.../HandlingNullValuesUnitTest.java:[12,18] this case label is dominated by a preceding case label

The reasoning behind this error is that there is no chance that the execution goes to the second case since any string object passed to the method would be handled in the first case itself.

这个错误背后的原因是没有机会执行到第二种情况,因为任何传递给方法的字符串对象都会在第一种情况下自己处理。

5.3. Handling Null Values

5.3 处理空值

In earlier versions of Java, each passing of a null value to a switch statement would result in a NullPointerException.

在早期的Java版本中,每次向switch语句传递一个null值都会导致一个NullPointerException

However, with type patterns, it is now possible to apply the null check as a separate case label:

然而,通过类型模式,现在可以将空值检查作为一个单独的案例标签来应用

static double getDoubleUsingSwitch(Object o) {
    return switch (o) {
        case String s -> Double.parseDouble(s);
        case null -> 0d;
        default -> 0d;
    };
}

If there is no null-specific case label, a pattern label of total type will match null values:

如果没有针对空值的案例标签,total类型的模式标签将匹配空值。

static double getDoubleUsingSwitchTotalType(Object o) {
    return switch (o) {
        case String s -> Double.parseDouble(s);
        case Object ob -> 0d;
    };
}

We should note that a switch expression cannot have both a null case and a total type case.

我们应该注意,一个switch表达式不能同时具有null情况和总类型情况。

Such a switch statement will result in the following compilation error:

这样的switch语句将导致以下编译错误。

[ERROR] Failed to execute goal ... on project core-java-17: Compilation failure
[ERROR] /D:/Projects/.../HandlingNullValuesUnitTest.java:[14,13] switch has both a total pattern and a default label

Finally, a switch statement using pattern matching can still throw a NullPointerException.

最后,使用模式匹配的switch语句仍然可以抛出NullPointerException

However, it can do so only when the switch block doesn’t have a null-matching case label.

然而,只有当switch块没有一个空匹配的案例标签时,它才能这样做。

6. Conclusion

6.结论

In this article, we explored pattern matching for switch expressions and statements, a preview feature in Java SE 17. We saw that by using patterns in case labels, that selection is determined by pattern matching rather than a simple equality check.

在这篇文章中,我们探讨了switch表达式和语句的模式匹配,这是Java SE 17中的一个预览功能。我们看到,通过在case标签中使用模式,该选择是由模式匹配而不是简单的平等检查决定的。

In the examples, we covered three different pattern types that can be applied in switch statements. Finally, we explored a couple of specific cases, including covering all values, ordering subclasses, and handling null values.

在这些例子中,我们涵盖了可以应用于switch语句的三种不同模式类型。最后,我们探讨了几个具体的案例,包括覆盖所有的值、对子类进行排序以及处理空值。

As always, the complete source code is available over on GitHub.

一如既往,完整的源代码可在GitHub上获得