皇上,还记得我吗?我就是1999年那个Linux伊甸园啊-----24小时滚动更新开源资讯,全年无休!

与Brian Goetz聊Java的模式匹配

作者 Michael Redlich   ,译者 薛命灯

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年代开始就已经采用了模式匹配,包括面向文本的编程语言SNOBOL4AWK、函数编程语言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:我们会继续探究如何将匹配器作为类成员,以及如何实现重载和继承。我们还有很多工作要做。

更多资源

查看英文原文:Brian Goetz Speaks to InfoQ on Pattern Matching for Java

转自 http://www.infoq.com/cn/news/2017/10/pattern-matching-for-java