作者
,译者InfoQ采访了来自Oracle的Java语言架构师Brian Goetz和编程语言研究员Gavin Bierman,谈论了有可能被集成到Java语言中的模式匹配。
动机
之所有要研究是否有可能在Java中加入模式匹配,主要还是为了改进Java的语言特性。假如有这样的一段代码:
if (obj instanceof Integer) { int intValue = ((Integer) obj).intValue(); // 使用intValue }
这段代码做了三个操作:
- 判断obj是否是一个Integer类型
- 将obj转成Integer类型
- 从Integer中抽取出int
现在再来看看在if…else结构的语句中判断其他类型。
String formatted = "unknown"; if (obj instanceof Integer) { int i = (Integer) obj; formatted = String.format("int %d", i); } else if (obj instanceof Byte) { byte b = (Byte) obj; formatted = String.format("byte %d", b); } else if (obj instanceof Long) { long l = (Long) obj; formatted = String.format("long %d", l); } else if (obj instanceof Double) { double d = (Double) obj; formatted = String.format("double %f", d); } else if (obj instanceof String) { String s = (String) obj; formatted = String.format("String %s", s); } ...
虽然上述的代码是可运行的,也很好理解,但写起来很枯燥(太多重复的样板代码),也容易产生bug。过多的样板代码会让业务逻辑变得含糊不清——如果instanceof方法已经判断出传入的实例是何种类型,那么就没必要重复进行转型了。
Goetz和Bierman解释了他们想要做出的改进。
我们认为是时候让Java拥抱模式匹配了,而不仅仅是把它作为一种临时的解决方案。很多编程语言从60年代开始就已经采用了模式匹配,包括面向文本的编程语言SNOBOL4和AWK、函数编程语言Haskell和ML,以及最近的面向对象编程语言Scala和C#。
模式由判断谓语(predicate)和一系列绑定变量(binding variable)组成,判断谓语被应用在目标上面,而绑定变量是从目标中抽取出来的。
if (x matches Integer i) { // 使用i }
Goetz和Bierman使用过各种模式匹配,它们使用了关键字matches和exprswitch。
matches操作符
matches操作符可以用来代替instanceof操作。例如:
String formatted;switch (obj) { case Integer i: formatted = String.format("int %d", i); break; case Byte b: formatted = String.format("byte %d", b); break; case Long l: formatted = String.format("long %d", l); break; case Double d: formatted = String.format(“double %f", d); break; default: formatted = String.format("String %s", s); }...
只有当变量x与某个Integer实例匹配时,变量i才能被使用。如果把Integer类型扩展到其他类型,那么if…else结构里的类型转换就可以省掉了。
switch的改进
Goetz和Bierman解释说,“switch语句就是一种最完美的模式匹配”。例如:
String formatted;switch (obj) { case Integer i: formatted = String.format("int %d", i); break; case Byte b: formatted = String.format("byte %d", b); break; case Long l: formatted = String.format("long %d", l); break; case Double d: formatted = String.format(“double %f", d); break; default: formatted = String.format("String %s", s); }...
上面的代码清晰易懂。不过,Goetz和Bierman也指出了switch的一个局限——“它只是一个语句,所以分支也必须是语句。我们希望可以把它们变成三元操作符那样的表达式,这样就可以保证只对其中的一个表达式求值”。
他们建议引入一种新的表达式语句——exprswtich。
String formatted = exprswitch (obj) { case Integer i -> String.format("int %d", i); case Byte b -> String.format("byte %d", b); case Long l -> String.format("long %d", l); case Double d -> String.format(“double %f", d); default -> String.format("String %s", s); }; ...
Goetz和Bierman建议的模式如下所述。
- 类型检测模式(将被转型的目标绑定到变量)
- 解构模式(解构目标并进行递归匹配)
- 常量模式(等值匹配)
- 变量模式(任意匹配并绑定目标)
- 下划线模式(任意匹配)
以下是Goetz与InfoQ的谈话内容。
InfoQ:在你发布论文后,社区都有哪些反馈?
Goetz:我们收到了非常积极的反馈。在其他语言里使用过模式匹配的人都很喜欢这个特性,他们也希望能够在Java中使用它。对于那些之前没有使用模式匹配的人,我们希望他们能够学会使用这个特性,我们认为很有必要在Java里添加这一特性。
InfoQ:Scala的匹配操作符对Java模式匹配的设计有多大的影响?有什么事情是Scala的匹配操作能做的而Java却做不到的吗?
Goetz:Scala只是众多启发我们在Java中加入模式匹配的语言之一。为一门语言添加特性不外乎从其他语言那里“移植”,但实际上,我们并不希望做得跟Scala完全一样,我们只要能够做到Scala的一部分,同时也能做Scala做不到的。
我们认为我们更有可能将模式匹配深度集成到对象模型中,比Scala有过之而无不及。Scala的模式是静态的,难以重载或覆盖。虽说能够做到这样已经很好了,但我们希望能够做得更好。
解构(deconstruction)是构造(construction)的另一面,面向对象编程语言让我们可以构造对象(构造器、工厂、构建器),而解构将给我们带来更丰富的API。虽说模式匹配与面向函数语言有一定的历史渊源,但我们相信它在面向对象语言里将会得到更好的发扬。
人们对语言特性津津乐道,不过我们认为语言特性真正的作用应该是为软件库提供更好的服务,而模式匹配将帮助我们写出更简单、更安全的软件库。
以java.lang.Class的两个方法为例:
public boolean isArray() { ... } public Class getComponentType() { ... }第二个方法需要以第一个方法返回true作为前提。对于API提供者(需要些更多代码,也需要更多的测试)和用户(容易出错)来说,涉及多API的逻辑操作就意味着复杂性。从逻辑上看,这两个方法就是一个模式,融合了“判断这个类是否是一个数组类”和“根据条件抽取组件类型”。如果能够通过以下的表达式来表达,那么代码写起来就更简单了,而且不容易出错。
if (aClass matches Class.arrayClass(var componentType)) { ... }
InfoQ:这次是否把让Scala rebase模式匹配作为目标(比如Scala 2.12就基于接口对trait进行了rebase)?
Goetz:与Lambda表达式一样,我们希望在设计这一语言特性的过程中,能够找到一些构建块,并把它们集成到底层的平台中,让其他语言也能从中获益,并为多个语言提供更好的互操作性。
InfoQ:Scala在实现这一特性时添加了很多额外的字节码用于支持解构case类型,这样会造成负面影响吗?
Goetz:这样做最大的问题在于,编译器不再只是个编译器了,它往类成员里添加了语义。虽然这样做很方便,但可能不是用户想要的,比如,比较数组要用Arrays.equals(),而不能用Object.equals()。
InfoQ:解构是否仅限于数据类(data class)?
Goetz:我们计划分几次来发布模式匹配功能,最开始先发布类型检测,然后是数据类的解构模式,最后是用户自定义的解构模式。虽说解构不会仅限于数据类,但这一过程还是需要一些时间。
InfoQ:你能够解释一下数据类和值类型(value type)之间的关系吗?
Goetz:它们之间是一种正交关系。值就是一种合体,没有标识。通过显式地拒用标识,运行时可以优化内存布局,扁平化对象头部,更自由地跨同步点缓存值组件。数据类简化了类表示和API协定之间复杂的关系,编译器就可以注入常用的类成员,如构造器、模式匹配器、equals方法、hashCode方法和toString方法。有些类可以被声明成值类(value class),有些则适合被声明成数据类,或者都不声明,或者都声明,这些情况都有可能。
InfoQ:Sealing特性是否需要源码编译器的支持?
Goetz:Sealing特性不仅仅需要编译器的支持,也需要JVM的支持,这样语言层面的约束——比如“X不能继承Y”——就可以在JVM层面得到加强。
InfoQ:Sealing是否意味着“只能在当前模块内扩展出子类”?
Goetz:Sealing可以有多种说法,最简单的就是“这个类只能在同一个源码文件中被扩展”——这是最常见的情况,也是最简单的。Sealing也可以被定义成“同一个包中”或“同一个模块中”,甚至可以是“友联(friend)”或复杂的运行时判断。
InfoQ:Java的新发布周期有助于模式匹配被集成到Java中吗?
Goetz:我们希望如此。我们已经将模式匹配分为几个小块,这样就可以快速地推出最简单的部分,然后继续开发其他部分。
InfoQ:什么时候可以看到原型?
Goetz:现在就有了,尝鲜者可以直接从源代码编译JDK。“Amber”上有一个分支已经可以支持类型检测模式和“matches”判断谓语。
InfoQ:你们将会怎样继续关于模式匹配的研究工作?
Goetz:我们会继续探究如何将匹配器作为类成员,以及如何实现重载和继承。我们还有很多工作要做。
更多资源
- Towards Pattern Matching in Java by Kerflyn, May 9, 2012
- Pattern Matching in Java by Benji Weber, May 3, 2014
- Pattern Matching in Java with the Visitor Pattern by Kevin Peterson, February 11, 2015
- Adventures in Pattern Matching by Brian Goetz, JVM Language Summit, August 2017
- Moving Java Faster by Mark Reinhold, September 6, 2017
- Java to Move to 6-Monthly Release Cadence by InfoQ, September 6, 2017
查看英文原文:Brian Goetz Speaks to InfoQ on Pattern Matching for Java
转自 http://www.infoq.com/cn/news/2017/10/pattern-matching-for-java