Java.next: 下一代JVM语言(二)
态类型语言(就像Groovy),它反映了一种义无反顾的设计决策。虽然Clojure允许你与遗留的Java程序进行全面而深度的交互,但是它并不试图构建一座桥梁去连接面向对象范式。例如,Clojure是函数式编程的铁杆,也支持面向对象以允许与该种范式进行互操作。尽管它支持面对对象程序员所习惯的全部特性,如多态--但,是以函数式风格,而非面向对象风格进行实现的。设计Clojure时遵循了一组核心的工程原则,如软件事务内存(Software Transactional Memory),这是为了迎合新功能而打破了旧有的编程范式。
编程范式
除语法之外,这些语言之间的最有趣的不同之处就是类型及其内在的编程范式:函数式或命令式。
静态类型 vs. 动态类型
编程语言中的静态类型要求显式的类型声明,例如Java中的int x;声明语句。动态类型语言并不要求在声明时提供类型信息。此处所考虑的语言都是强类型语言,意即程序在赋值之后能够反射出类型。
Java的类型系统广受诟病之处就是其静态类型有太多不便,且又没有提供足够的益处。例如,在当前的有限的类型推导出现之前,Java要求开发者在赋值语句两边要重复地声明类型。Scala的类型比Java的更为静态,但在日常使用中所遇到的不便要少得多,因为它大量使用了类型推导。
初看Groovy,它似乎有一种行为能够衔接静态与动态之间的隔阂。考虑如清单2所示的简单对象集合工厂:
清单2. Groovy集合工厂
class CollectionFactory {
def List getCollection(description) {
if (description == "Array-like")
new ArrayList()
else if (description == "Stack-like")
new Stack()
}
}
清单2中的类表现为一个工厂类,基于传入的description参数,该工厂返回List接口的两种实现--ArrayList或Stack--之一。对于Java开发者,上述代码确保了返回值能够符合约定。然后,清单3中的两个单元测试揭示了一种复杂性:
清单3. Groovy中的集合类型测试
@Test
void test_search() {
List l = f.getCollection("Stack-like")
assertTrue l instanceof java.util.Stack
l.push("foo")
assertThat l.size(), is(1)
def r = l.search("foo")
}
@Test(expected=groovy.lang.MissingMethodException.class)
void verify_that_typing_does_not_help() {
List l = f.getCollection("Array-like")
assertTrue l instanceof java.util.ArrayList
l.add("foo")
assertThat l.size(), is(1)
def r = l.search("foo")
}
在清单3中的第一个单元测试中,使用前述的工厂类获得一个Stack对象,并验证它是否确实是Stack对象,然后再执行栈操作,例如push(),size()和search()。然而,在第二个单元测试中,我必须声明一个期望的异常MissingMethodException才能确保该测试能够通过。当我获取一个Array-like的集合,并将它赋给List类型的变量时,我能够验证返回的类型确为一个List对象。但是,当我试图调用search()方法时将触发异常,因为ArrayList并不包含search()方法。因此,这种声明无法在编译时确保方法的调用是正确的。
虽然这看起来像是一个缺陷,但这种行为却是恰当的。Groovy中的类型只是确保赋值语句的有效性。例如,在清单3中,如果返回的实例未实现List接口,将会触发一个运行时异常GroovyCastException。鉴于此,可以肯定Groovy能够与Clojure同跻身于强动态类型语言家族。
然而,Groovy最新的一些变化使得它的静态与动态之间的隔阂变得扫地清。Groovy 2.0加入了注解@TypeChecked,该注解可使你特别地对类或方法决定进行严格的类型检查。清单4例证该注解的使用:
清单4. 使用注解的类型检查
@TypeChecked
@Test void type_checking() {
def f = new CollectionFactory()
List l = f.getCollection("Stack-like")
l.add("foo")
def r = l.pop()
assertEquals r, "foo"
}
在清单4中,我加入了注解@TypeChecked,它同时对赋值及随后的方法调用进行了验证。例如,清单5中的代码将不能通过编译:
清单5. 防止无效方法调用的类型检查
@TypeChecked
@Test void invalid_type() {
def f = new CollectionFactory()
Stack s = (Stack) f.getCollection("Stack-like")
s.add("foo")
def result = s.search("foo")
}
在清单5中,我必须对集合工厂返回的对象进行强制类型转换,这样才能允许我调用Stack类中的search()方法。但这种方式会产生一些局限性:当使类型静态化之后,Groovy的很多动态特性将无法工作。然而,上述救命证明了Groovy将继续进行改进,以弥合静态性与动态性之间的分歧。
所有这些语言都有十分强大的元编程功能,所以更为严苛的类型化可以在事后再添加进来。例如,已有多个分支项目将选择性类型(selective type)引入到Clojure中。但一般认为选择性类型是可选的,它不是类型系统的一部分;它只是一个类型验证系统。
命令式 vs. 函数式
另一个主要的比较维度就是命令式与函数式。命令式编程注重于单步执行的结构,在许多情况下,它是模仿了早期底层硬件的有益结构。函数式编程则注重将函数作为第一等的结构体,以试图将状态传递与可变性降低到最小。
Groovy在很大程度上是受Java的启发,它在根本上仍然是命令式语言。但从一开始,Groovy就加入了许多函数式命令的特性,并且以后还会加入更多的此类特性。
Scala则弥合了这两种编程范式,它同时支持这两种范式。在更偏向(也更鼓励)函数式编程的同时,Scala依然支持面向对象和命令式编程。因此,为了恰当地使用Scala,就要求团队要受到良好的
培