前言
前段时间 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。
- JEP 305 Pattern Matching for instanceof (Preview)
- JEP 375 Pattern Matching for instanceof (Second Preview)
- JEP 394 Pattern Matching for instanceof
Java 团队认为 ”switch 语句就是一种完美的模式匹配“,所以它们也为 switch 语句作了改进,该功能已经在 Java14 中 Release
- JEP 325: Switch Expressions (Preview)
- JEP 354: Switch Expressions (Second Preview)
- JEP 361: Switch Expressions
随后就引入了 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) 即可,如下图所示:
项目构建工具如果是 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 相比还有很长的路要走,曾经的先驱在某些方面现在已经成为了追赶者,未来几何,作为吃瓜群众咱们拭目以待。