详解 Java 17 中的模式匹配(Pattern Matching)

提到模式匹配(Pattern Matching),Java 开发人员可能会比较陌生。实际上,其他编程语言的开发人员早就已经使用过模式匹配了。JVM 上的编程语言 Scala 的模式匹配功能就很强大。

什么是模式匹配?

为了更好地解释模式匹配,我们从一个简单的例子开始。我们希望创建一个方法,可以把任何对象转换成 String 格式。这就需要根据对象的类型来进行不同的格式化操作。我们可以很容易就写出下面这样的代码。这段代码的核心是使用 instanceof 操作符来检查输入对象的类型,再根据对象类型进行格式化操作。

public class ObjectFormatter {

  public String format(Object input) {
    if (input == null) {
      return "";
    } else if (input instanceof Number) {
      return NumberFormat.getNumberInstance().format(input);
    } else if (input instanceof LocalDateTime) {
      return ((LocalDateTime) input).format(DateTimeFormatter.ISO_DATE_TIME);
    } else {
      return input.toString();
    }
  }
}

上述对 instanceof 操作符的使用就是模式匹配的一种简单形式。

一个模式由匹配 predicate 和模式变量的集合组成

  • 匹配 predicate 判断一个模式是否匹配目标对象。
  • 如果模式匹配的话,模式变量的集合用来从目标对象中提取值。

在 instanceof 操作符的例子中,匹配 predicate 的作用是检查目标对象的类型,而模式变量的集合中只有一个变量,就是目标对象自身。这种类型的模式,被称为类型模式(type pattern)。

模式匹配是一个涵盖范围非常大的功能。根据现在 Java 的发布周期,模式匹配的内容会在不同的 Java 版本中逐渐添加进来。具体的发布周期可以参考下面的表格。这个表格的右侧三列表示的是不同的模式匹配类型,每一行表示这些模式匹配类型在对应 Java 版本中的可用状态。

Java版本

instanceof

switch

记录类型

Java 14

预览



Java 15

二次预览



Java 16

正式功能



Java 17

正式功能

预览


Java 18

正式功能

二次预览


Java 19

正式功能

三次预览

预览

以 Java 17 为例,可以使用 instance 模式的正式功能,以及 switch 模式的预览功能。

Java 18 和 Java 19 中可用的模式匹配功能也列在了表格中,作为参考。

instanceof 模式匹配

Java 中的 instanceof 操作符用来检查对象的类型。下面的代码给出了通常使用 instanceof 操作符的模式。在 if 语句中使用 instanceof 来进行检查,如果检查通过,则使用强制类型转换,把输入对象 obj 转换成 String 类型的 s,最后再使用变量 s。

if (obj instanceof String) {
  String s = (String) obj;
}

从上述代码中可以看到,对 instanceof 操作符的使用模式是非常繁琐的,其中需要检查的目标类型 String 就出现了三次。在使用了 instanceof 模式匹配之后,代码可以简化很多。在下面的代码中, String s 表示类型模式,其中 String 是需要匹配的类型,s 是匹配成功之后用来捕获对象的变量。该变量 s 可以直接在 if 语句块中使用。

if (obj instanceof String s) {
  System.out.println(s.toUpperCase());
}

模式变量使用的是流作用域(flow scoping)。一个模式变量能够出现在作用域中,当且仅当编译器可以推断出模式匹配必定成功,并且该变量被赋予了一个值时。在上面的例子中,if 语句块的代码只有在模式匹配了之后才会执行,变量 s 此时必定被赋予了值 obj,因此编译器可以确定 s 必定在 if 语句块的作用域中。

关于流作用域,其实不用了解太多。如果使用错误,编译器会提示你的。

下面的代码给出了 instanceof 模式匹配的代码示例。第一个 if 匹配 String 类型的同时,加上了对字符串长度的检查;第二个 if 匹配剩下的 String 类型的对象。

public class StringMatch {

  public void test(Object obj) {
    if (obj instanceof String s && s.length() > 10) {
      System.out.println("长字符串 -> " + s);
    } else if (obj instanceof String s) {
      System.out.println("短字符串 -> " + s);
    } else {
      System.out.println("其他");
    }
  }
}

switch 的模式匹配

switch 的模式匹配在 Java 17 中是预览功能,因此需要通过命令行参数 --enable-preview 来启用。switch 在很多时候可以替代嵌套的 if/else。

下面的代码使用 switch 模式匹配改写了上面的使用嵌套 if/else 的代码示例。使用 switch 比 if/else 更加简洁。

public class StringMatch {

  public void test(Object obj) {
    switch (obj) {
      case String s && s.length() > 10 -> System.out.println(
          "长字符串 -> " + s);
      case String s -> System.out.println("短字符串 -> " + s);
      default -> System.out.println("其他");
    }
  }
}

我们可以用 switch 改写文章开头提到的对象格式化的方法,如下面的代码所示。使用 switch 的代码更加简洁易懂。

public class ObjectFormatter {

  public String format(Object input) {
    return switch (input) {
      case null -> "";
      case Number n -> NumberFormat.getNumberInstance().format(n);
      case LocalDateTime t -> t.format(DateTimeFormatter.ISO_DATE_TIME);
      default -> input.toString();
    };
  }
}
发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章