Java17 之 Pattern Matching 初探
2021/10/18 · vran

前言

前段时间 Java17 发布了,恰好在 Release note 中看到了 Pattern Matching 的信息,这极大的引起了我的兴趣,遂初步尝试了一番,以本文作为记录。

Pattern Matching 一般译作模式匹配,它其实并不是什么新的概念,C#、Scala、Hashkell 等其他语言在很早以前就支持了。

模式匹配里的模式指的不是设计模式,而是数据结构的模式,常见的有类型模式、结构模式、变量模式以及常量模式等。

回首过往

由于现在 Java 改进了迭代速度和发布周期, 采取了”小步快跑,快速迭代“的策略,所以像 Pattern Matching 这种大的改进也被拆分成了多个 Roadmap。

从 Java12 开始,Java 团队就在为引入 Pattern Matching 而努力了,他们不想做得跟 Scala 完全一样,而是希望有过之而无不及。

最开始的 Pattern Matching 是针对 instanceof 操作符的强化,光这项改进就经历了三个版本:从 Java14 开始,直到 Java16 后才 Release。

Java 团队认为 ”switch 语句就是一种完美的模式匹配“,所以它们也为 switch 语句作了改进,该功能已经在 Java14 中 Release

随后就引入了 Pattern Matching for switch 的提案,目前该特性已作为预览功能随着 Java17 发布了

最新的动向当属 JEP 405,但目前还处于候选状态

接下来就挨个试试 Java 已经完成了的 Pattern Matching 吧

本文使用工具版本如下

  • Oracle Java17
  • Intellij IDEA 2021.2.3
  • Gradle 7.3-rc-3

小目标:先加强个 instanceof

instanceof 是 Java 中的一个二元操作符,作用是测试它左边的对象是否是它右边的类的实例(类型匹配),比如

// obj 对象是否为 String 类型
public static boolean isString(Object obj) {
    return obj instanceof String;
}

常用于类型强转前的判断,避免 ClassCastException

public static void testInstanceof(Object obj) {
    if (obj instanceof String) {
        String str = (String) obj;
        System.out.println(str);
    }
}

几乎所有的 Java 项目中都会有这样的代码,写这样的代码也让人感觉非常的乏味和重复。好在 Java 团队也意识到了这个问题,于是他们对 instanceof 操作符作了加强,使得 test and cast 可以合二为一,如下:

public static void testInstanceof(Object obj) {
		// 如果匹配成功,则直接赋值给变量 str
    if (obj instanceof String str) {
        System.out.println(str);
    }
}

不止如此,它可以使用复杂的条件判断:

public static boolean isLargerString(Object obj) {
    if (obj instanceof String str && str.length() > 68) {
        return true;
    } else {
        return false;
    }
}

再加强个 switch

Pattern Matching for Switch 是 Java17 的预览特性,所以读者如果要运行以下代码的话至少需要安装 Java17,在编译时加上启用预览的参数才行(参考文档):

javac --enable-preview --release 17 Demo.java

如果读者也是使用的 Intellij IDEA,只需要将 Project 配置中的 Project language level 设置为 17(preview) 即可,如下图所示:

setting

项目构建工具如果是 gradle 的话,最低版本需要 7.3-rc-1 才支持 Java17。

准备工作完成以后就可以体验 Java17 的 Pattern Matching for switch 了,不过这之前得先提一下 Java14 对 switch 的增强。

Java14 后 switch 支持直接返回值,只需要使用 -> 代替以前的 : 即可:

public static void testSwitch(Integer num) {
    String str = switch (num) {
        case 1 -> "one";
        case 2 -> "two";
        case 3 -> "three";
        default -> "unknown";
    };
}

也支持执行表达式,而且不需要像以前一样要注意手动 break,每个表达式被匹配执行完以后就终止了

public static void testSwitch2(Integer num) {
    switch (num) {
        case 1 -> System.out.println("one");
        case 2 -> System.out.println("two");
        case 3 -> System.out.println("three");
        default -> System.out.println("unknown");
    }
}

上面的代码展示的是常量匹配模式,新版的 switch 表达式也支持类型匹配模式:

public static void testSwitch3(Object obj) {
    String formatted = switch (obj) {
        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        -> obj.toString();
    };
    System.out.println(formatted);
}

当然,也支持守卫模式

public static void testSwitch4(Object obj) {
    String formatted = switch (obj) {
        case null -> "null";
        case String str && str.length() > 64 -> "long string";
        case String str -> "normal string";
        default        -> obj.toString();
    };
    System.out.println(formatted);
}

下一步:Record Patterns 和 Array Patterns

Record Patterns & Array Patterns 是 JEP 405 的提案内容,目标就是使得 Pattern Matching 可以应用于 Record class 和数组类型上。

PS: Record 是 Java16 发布的一种新的数据类型,详情查看 JEP 395。

该提案目前尚处于 Candidate 的状态,不过我们仍可以简单的了解一下。

首先是 Record class 和 instanceof 操作符的结合,使得 instanceof 不仅能够匹配类型,还能够直接结构 Record 类型中的属性,如下:

public record Point(int x, int y) {}

public record 3DPoint(int x, Point point) {}

public static void testMatch(Point point, 3DPoint 3dPoint) {
    // 简单解构模式匹配
    if (point instanceof Point(int x, int y)) {
        System.out.println(x);
        System.out.println(y);
        System.out.println(point);
    }

    // 嵌套解构模式匹配
    if (3dPoint instanceof 3DPoint(int x, Point(int z, int y))) {
        System.out.println(x);
        System.out.println(y);
        System.out.println(z);
    }
}

然后就是 Array 和 instanceof 操作符的结合

// 将数组前两个字符拼接并打印
static void printFirstTwoStrings(Object o) {
		// 这里的 ... 标识 0 个或多个剩余元素
    if (o instanceof String[] { String s1, String s2, ... }){
        System.out.println(s1 + s2);
    }
}

由于该提案尚且处于 Candidate 的状态,本文就不多做介绍了,如果有兴趣的话可以通过该链接持续关注:https://openjdk.java.net/jeps/405

总结

目前看来 Java 的 Pattern Matching 与 Scala 相比还有很长的路要走,曾经的先驱在某些方面现在已经成为了追赶者,未来几何,作为吃瓜群众咱们拭目以待。

参考

  1. Pattern Matching
  2. 《与 Brian Goetz 聊 Java 的模式匹配》
  3. Scala docs:模式匹配
  4. 话说模式匹配(1) 什么是模式?
over