前言
Java 传承的是平台,而不是语言本文来源gaodaima#com搞(代@码$网6。有超过 200 种语言可以在 JVM 上运行,它们之中不可避免地会有一种语言最终将取代 Java 语言,成为编写 JVM 程序的最佳方式。本系列将探讨三种下一代 JVM 语言:Groovy、Scala 和 Clojure,比较并对比新的功能和范例,让 Java 开发人员对自己近期的未来发展有大体的认识。
Java 语言的开发人员精通 C++ 和其他语言,包括多继承(multiple inheritance),使得类可以继承自任意数量的父类。多继承带来的一个问题是,不可能确定所继承的功能来自哪个父类。这个问题被称为钻石问题(请参阅 参考资料)。钻石问题和多继承中固有的其他复杂性启发了 Java 语言设计者选择 “单继承加接口” 的方法。
接口定义了语义,但没有定义行为。它们非常适合用来定义方法签名和数据抽象,所有 Java 下一代语言都支持 Java 接口,并且无需进行重大的修改。不过,有些交叉问题不适合使用 “单继承加接口” 模型。这种错位导致必须提供适合 Java 语言的外部机制,比如面向方面的编程。两种 Java 下一代语言(Groovy 和 Scala)通过使用一种被称为混入 或特征 的语言结构在另一个层次的扩展上处理这类问题。本文介绍了 Groovy 中的混入和 Scala 中的特征,并演示了如何使用它们。(Clojure 通过协议处理大致相同的功能,我在 Java 下一代:没有继承性的扩展,第 2 部分 中已经介绍过这一点。)
混入
混入的概念起源于 Flavors 语言(请参阅 参考资料)。这个概念的灵感来自于开发该语言的办公室附近的一家冰淇淋店。这家冰淇淋店提供了纯口味的冰淇淋,以及客户想要的其他任何的 “混合物”(糖果碎、糖屑、坚果,等等)。
早期的一些面向对象语言在单个代码块中共同定义某个类的属性和方法,所有类定义是完整的。在其他语言中,开发人员可以在一个地方定义属性,但推迟方法的定义,并在适当的时候将它们 “混合” 到类中。随着面向对象语言的演变,混入与现代语言的配合方式的细节也在演变。
在 Ruby、Groovy 和类似的语言中,作为一个接口和父类之间的交叉,混入可以扩充现有的类层次结构。像接口一样,混入可以充当 instanceof 检查的类型,同时也要遵循相同的扩展规则。您可以将无限数量的混入应用于某一个类。与接口不同的是,混入不仅指定了方法签名,也可以实现签名的行为。
在包括混入的第一种语言中,混入只包含方法,不包含状态,例如,成员变量。现在很多语言(Groovy 也在其中)都包括有状态的混入。Scala 的特征也以有状态的方式进行操作。
Groovy 的混入
Groovy 通过 metaClass.mixin() 方法或 @Mixin 注解来实现混入。(@Mixin 注解依次使用 Groovy Abstract Syntax Tree (AST) 转换,以支持所需的元编程管道。)清单 1 中的示例使用 metaClass.mixin() 让 File 类能够创建 ZIP 压缩文件:
清单 1. 将 zip() 方法混合到 File 类中
class Zipper { def zip(dest) { new ZipOutputStream(new FileOutputStream(dest)) .withStream { ZipOutputStream zos -> eachFileRecurse { f -> if (!f.isDirectory()) { zos.putNextEntry(new ZipEntry(f.getPath())) new FileInputStream(f).withStream { s -> zos << s zos.closeEntry() } } } } } static { File.metaClass.mixin(Zipper) } }
在清单 1 中,我创建了一个 Zipper 类,它包含新的 zip() 方法,以及将该方法添加到现有 File 类的连接。zip() 方法的(不起眼的)Groovy 代码以递归方式创建了一个 ZIP 文件。清单的最后一部分通过使用静态的初始化程序,将新方法添加到现有的 File 类。在 Java 语言中,类的静态初始化程序在加载类的时候运行。静态初始化程序是扩充代码的理想位置,因为在运行依赖于增强的任何代码之前,应确保先运行初始化程序。在 清单 1 中,mixin() 方法将 zip() 方法添加到 File。