写点什么

Groovy 2.0 新特性

2012 年 10 月 19 日

新发布的 Groovy2.0 为这门语言带来了关键的静态特性:静态类型检查静态编译;采用了 JDK 7 相关的改进:Project Coin 语法增强新支持的“invoke dynamic” JVM 指令;同时,提高了模块化。我们将在这篇文章中了解这些新特性的详情。

面向动态语言的“静态主题”

静态类型检查

Groovy 天生而且永远都是动态语言。但 Groovy 也常被当作"Java 脚本语言",或是“更好的 Java”(即限制更少且功能更强的 Java)。实际上,许多 Java 开发者将 Groovy 作为一种扩展语言使用或嵌入到自己的 Java 应用中,如编写表现力更强的业务规则、为不同客户进一步定制应用等。对于这种面向 Java 的使用场景,开发者并不需要这门语言提供的所有动态能力,他们通常期望能从 Groovy 编译器得到跟 javac 编译器一样的反馈。特别是,他们希望得到编译错误(而非运行时错误),如变量或方法名的拼写错误、错误的类型赋值等。这就是 Groovy 2 支持静态类型检查的原因。

指出明显的拼写错误

静态类型检查器建立在 Groovy 已有、强大的 AST(抽象语法树)之上,不熟悉它们的开发者可以将其视为一种利用注解触发的可选编译器插件。作为可选特性,不需要它时,Groovy 不会强制你使用。要触发静态类型检查,只需在方法或类上使用@TypeChecked注解就可以在你期望的粒度级别打开检查。让我们首先看一个示例:

<b>import</b> groovy.transform.TypeChecked<p><b>void</b> someMethod() {}</p><p>@TypeChecked</p><br></br><b>void</b> test() {<br></br>    <i>// 编译错误:</i><br></br>    <i>// 找不到匹配的 sommeeMethod()</i><br></br>    sommeeMethod()<p>    <b>def</b> name = "Marion"</p><p>    <i>// 编译错误:</i></p><br></br>    <i>// 没有声明变量 naaammme</i><br></br>    println naaammme<br></br>}我们用@TypeChecked注解了test()方法,它告诉 Groovy 编译器在编译时对指定的方法进行静态类型检查。我们试图调用带有明显拼写错误的someMethod(),并打印另一个拼错的 name 变量,编译器会分别抛出 2 个编译错误,因为找不到对应的方法和变量声明。

检查赋值和返回值

静态类型检查器还会验证返回类型和赋值是否一致:

<b>import</b> groovy.transform.TypeChecked<p>@TypeChecked</p><br></br><b>Date</b> test() {<br></br>    <i>// 编译错误:</i><br></br>    <i>// 不能把 Date 赋给 </i><br></br>    <i>// int 类型的变量 </i><br></br>    <b>int</b> object = <b>new</b> Date()<p>    String[] letters = ['a', 'b', 'c']</p><br></br>    <i>// 编译错误:</i><br></br>    <i>// 不能把 String 类型的值赋给 </i><br></br>    <i>// Date 类型的变量 </i><br></br>    Date aDateVariable = letters[0]<p>    <i>// 编译错误:</i></p><br></br>    <i>// 无法在返回 Date 类型的方法中 </i><br></br>    <i>// 返回 String 类型的值 </i><br></br>    <b>return</b> "today"<br></br>}在这个示例中,编译器会抱怨这样的事实:你没法把Date赋给int变量,也没法返回String来取代方法签名中指定的Date。正中间脚本引起的编译错误也很有趣,因为它不仅抱怨了错误的赋值,而且还因为它展示了动态类型推断的能力,这当然是由于类型检查器知道letters[0]String类型,因为我们正在处理一个String数组。

类型推断的更多细节

既然谈到了类型推断,那我们就看看它的一些其他表现形式。我们曾说过类型检查器会跟踪返回类型和值:

<b>import</b> groovy.transform.TypeChecked<p>@TypeChecked</p><br></br><b>int</b> method() {<br></br>    <b>if</b> (<b>true</b>) {<br></br>        <i>// 编译错误:</i><br></br>        <i>// 无法在返回 int 类型的方法中 </i><br></br>        <i>// 返回 String 类型的值 </i><br></br>        'String'<br></br>    } <b>else</b> {<br></br>        42<br></br>    }<br></br>}若方法返回原始类型的int值,类型检查器还能够检查出不同结构的返回值,如if/else分支、try/catch块或switch/case块。在该示例中,if/else块的一个分支试图返回一个String值而非原始类型的int,这时编译器就开始抱怨了。

常见的类型转换依然可用

但静态类型检查器不会对 Groovy 支持的某些自动类型转换进行抱怨。例如,对于返回String、booleanClass的方法签名,Groovy 会自动将返回值转换到这些类型:

<b>import</b> groovy.transform.TypeChecked<p>@TypeChecked</p><br></br><b>boolean</b> booleanMethod() {<br></br>    "non empty strings are evaluated to true"<br></br>}<p><b>assert</b> booleanMethod() == <b>true</b></p><p>@TypeChecked</p><br></br>String stringMethod() {<br></br>    <i>// 调用 toString() 将 StringBuilder 转换成 String</i><br></br>    <b>new</b> StringBuilder() << "non empty string"<br></br>}<p><b>assert</b> stringMethod() <b>instanceof</b> String</p><p>@TypeChecked</p><br></br><b>Class</b> classMethod() {<br></br>    <i>// 会返回 java.util.List 类 </i><br></br>    "java.util.List"<br></br>}<p><b>assert</b> classMethod() == List</p>静态类型检查器的智能足以完成类型推断

<b>import</b> groovy.transform.TypeChecked<p>@TypeChecked</p><br></br><b>void</b> method() {<br></br>    <b>def</b> name = " Guillaume "<p>    <i>// 判断出是 String 类型(就算它是在 GString 中)</i></p><br></br>    println "NAME = ${name.toUpperCase()}"<p>    <i>// 支持 Groovy GDK 方法 </i></p><br></br>    <i>// (也支持 GDK 操作符重载)</i><br></br>    println name.trim()<p>    <b>int</b>[] numbers = [1, 2, 3]</p><br></br>    <i>// 元素 n 是 int</i><br></br>    <b>for</b> (<b>int</b> n <b>in</b> numbers) {<br></br>        println <br></br>    }<br></br>}尽管name变量是用def定义的,但类型检查器还是知道它是String类型。接下来,当这个变量被插入用在 string 中时,它知道 name 变量能调用String 的 toUpperCase()方法,或者之后的trim()方法,该方法是由 Groovy Development Kit 添加用来装饰String类的。最后,当循环原始的int数组时,它还知道数组中的元素明显就是int

动态特性和静态化类型方法的混合使用

记住一点很重要:使用静态类型检查工具会限制你能在 Groovy 中使用的特性。大多数运行时动态特性是不允许的,因为它们没法在编译时被静态类型检查。因此,通过类型的元类(metaclass)在运行时添加一个新方法是不允许的。但是,当你需要使用一些特殊的动态特性时,比如 Groovy 的构建器(builder),只要愿意,你可以选择不使用静态类型检查。

@TypeChecked注解可用于类或方法级别。因此,要是想对整个类进行类型检查,就把它用在类上,若只想对某些方法进行类型检查,可以把它用在那些方法上。此外,若想对所有内容进行类型检查,但排除某个特殊方法,你可以对被排除方法使用@TypeChecked(TypeCheckingMode.SKIP) - 或简化版本@TypeChecked(SKIP),前提是你静态导入了相关枚举。以下脚本说明了这种情况,greeting()方法需要类型检查,而generateMarkup()方法不需要:

<b>import</b> groovy.transform.TypeChecked<br></br><b>import</b> groovy.xml.MarkupBuilder<p><i>// 这个方法和它的代码要进行类型检查 </i>@TypeChecked</p><br></br>String greeting(String name) {<br></br>    generateMarkup(name.toUpperCase())<br></br>}<p><i>// 这个方法不需要类型检查 </i><i>// 并且你可以使用像 markup builder 这样的动态特性 </i>String generateMarkup(String name) {</p><br></br>    <b>def</b> sw =<b>new</b> StringWriter()<br></br>    <b>new</b> MarkupBuilder(sw).html {<br></br>        body {<br></br>            div name<br></br>        }<br></br>    }<br></br>    sw.toString()<br></br>}<p><b>assert</b> greeting("Cédric").contains("<div>CéDRIC</div>")</p>## 类型推断和 instanceof 检查

当前的 Java 发行版不支持通用的类型推断;因此今天我们发现很多地方的代码往往相当冗长并且结构混乱。这掩盖了代码的意图,而且没有强大的 IDE 支持也很难写代码。这是instanceof检查的应用场景:你经常会在if条件中使用 instanceof 检查值的类,并且在if块之后,你必须使用对象转型(cast)才能使用这个对象值的方法。用一般的 Groovy 代码,结合新的静态类型检查模式,你可以彻底摆脱那些对象转型。

<b>import</b> groovy.transform.TypeChecked<br></br><b>import</b> groovy.xml.MarkupBuilder<p>@TypeChecked</p><br></br>String test(Object val) {<br></br>    <b>if</b> (val <b>instanceof</b> String) {<br></br>        <i>// 不同于 Java 的写法:</i><br></br>        <i>// return ((String)val).toUpperCase()</i><br></br>        val.toUpperCase()<br></br>    } <b>else</b> <b>if </b>(val <b>instanceof</b> Number) {<br></br>        <i>// 不同于 Java 的写法:</i><br></br>        <i>// return ((Number)val).intValue().multiply(2)</i><br></br>        val.intValue() * 2<br></br>    }<br></br>}<p><b>assert</b> test('abc') == 'ABC'</p><br></br><b>assert</b> test(123) == '246'在上面的示例中,静态类型检查器知道参数 val 在if块中是String类型,在 else if 块中是Number,无需任何转换。

最低上限

静态类型检查器在类型推断方面走得更远,从某种意义上讲它对你的对象类型了解更精细。考虑下面的代码:

<b>import</b> groovy.transform.TypeChecked<p><i>// 推断返回类型:</i><i>// 一个可比较和可序列化的数字列表 </i>@TypeChecked test() {</p><br></br>    // 一个整型和一个 BigDecimal<br></br>    <b>return</b> [1234, 3.14]<br></br>}在这个示例中,凭直觉,我们返回了一组数字:一个Integer和一个BigDecimal。但是静态类型检查器计算了我们所说的 _“最低上限(lowest upper bound)”_,它实际上是一个数字列表,而且是可序列化和可比较的。用标准 Java 类型符号不可能表示该类型,但如果我们有一些类似与操作(&)的交集操作符,它看起来就像List<Number & Serializable & Comparable>。

流式转型(Flow typing)

尽管其实不应该将这种做法视为好实践,但有时开发者会使用相同的无类型变量来存储不同类型的值。看看方法体:

<b>import</b> groovy.transform.TypeChecked<p>@TypeChecked test() {</p><br></br>    <b>def</b> var = 123             <i>// 推断出的类型是 int</i><br></br>    var = "123"               <i>// 用一个 String 给 var 赋值 </i><p>    println var.toInteger()   <i>// 没问题,不需要转型 </i></p><p>    var = 123</p><br></br>    println var.toUpperCase() <i>// 出错了,var 是 int 型!</i><br></br>}``var变量一开始被初始化为int。然后,被赋给一个String。_“流式转型(flow typing)”_ 算法根据赋值流程知道变量现在持有一个String,所以静态类型检查器会乐于接受由 Goovy 添加到String上的toInteger()方法。接下来,一个数字被放回到 var 变量中,但是紧接着调用toUpperCase()时,类型检查器将抛出一个编译错误,因为Integer上没有toUpperCase()方法。

对于被共享给对其感兴趣的闭包中的变量,流式转型算法有些特殊的情况。当局部变量被定义该变量的方法中的闭包引用时,会发生什么?看看这个示例:

<b>import</b> groovy.transform.TypeChecked<p>@TypeChecked test() {</p><br></br>    <b>def</b> var = "abc"<br></br>    <b>def</b> cl = {<br></br>        <b>if</b> (<b>new</b> Random().nextBoolean()) var = <b>new</b> Date()<br></br>    }<br></br>    cl()<br></br>    var.toUpperCase() // 编译错误!<br></br>}局部变量var被赋值为String,但接着,若某个随机值为真,var可能会被赋值为Date。一般情况下,只有在运行时我们才确切知道闭包的 if 语句中的条件为真还是假。因此,编译器不可能在编译时知道var现在是String还是Date。这就是编译器对于toUpperCase()调用抱怨的原因,因为它无法推断变量包含的是String。这个例子虽略显做作,但是下面有一些有趣的例子:

<b>import</b> groovy.transform.TypeChecked<p><b>class</b> A           { <b>void</b> foo() {} }</p><br></br><b>class</b> B <b>extends</b> A { <b>void</b> bar() {} }<p>@TypeChecked test() {</p><br></br>    <b>def</b> var = <b>new</b> A()<br></br>    <b>def</b> cl = { var = <b>new</b> B() }<br></br>    cl()<br></br>    <i>// var 起码是个 A 的实例 </i><br></br>    <i>// 所以我们允许调用 foo() 方法 </i><br></br>    var.foo()<br></br>}在上面的test()方法中,var被赋予A的一个实例,然后在闭包中被赋予B的一个实例,因此我们至少可推断出 var 类型A

所有这些添加到 Groovy 编译器中的检查都是在编译时完成的,但是生成的字节码像往常一样仍是相同的动态码 - 在行为上根本没变。

由于编译器现在知道你程序中类型方面的很多事情,它向许多有趣的能力敞开了大门:静态编译那些被类型检查的代码怎样?除了其他优势,一个明显优势是生成的字节码将更接近于由 javac 编译器自己生成的字节码,让静态编译过的 Groovy 代码跟纯 Java 代码一样快。在下一节,我们将了解更多关于 Groovy 静态编译的内容。

静态编译

正如我们将在以下关于向 JDK 7 靠齐的章节中看到的,Groovy 2.0 支持 JVM 新的 _“invoke dynamic”_ 指令及其相关 API,它们简化了 Java 平台上动态语言的开发并为 Groovy 的动态调用带来了额外的性能提高。可不幸的是,在本文撰写时,JDK 7 尚未被部署于生产环境,因而并非所有人都有机会运行最新版本。所以期待性能改进的开发者若没法运行在 JDK 7 上,就不会在 Groovy 2.0 中看到太多的改变。所幸,Groovy 开发团队考虑到了这些开发者(除了其他改进之外)会对性能改进感兴趣,其手段就是允许类型检查后的代码代码可被静态编译。

废话少说,让我们现在就亲手试试新的@CompileStatic注解:

<b>import</b> groovy.transform.CompileStatic<p>@CompileStatic</p><br></br><b>int</b> squarePlusOne(<b>int</b> num) {<br></br>    num * num + 1<br></br>}<p><b>assert</b> squarePlusOne(3) == 10</p>这次使用的是@CompileStatic,而非@TypeChecked,并且你的代码会被静态编译,同时生成的字节码非常像 javac 的字节码,运行速度一样。就像@TypeChecked注解,@CompileStatic能注解类和方法,@CompileStatic(SKIP)可以让某个方法在其所属类被@CompileStatic标记时不被静态编译。

生成类 javac(javac-like)字节码的另一好处是那些被注解的方法的字节码大小会比通常 Groovy 为动态方法生成的字节码的大小要小,因为要支持 Groovy 的动态特性,动态场景下的字节码包含了调用 Groovy 运行时系统的额外指令。

最后一点值得注意的是,框架或库代码作者可使用静态编译,这有助于避免当代码库中多个部分使用动态元编程时的负面影响。像 Groovy 这类语言中可用的动态特性给开发者带来了极强的能力和灵活性,但鉴于元编程特性是动态发挥作用的,若不加注意,不同的假设会存在于系统的不同部分,由此产生意想不到的后果。举一个例子(虽然有点刻意为之),假设你在使用两个不同的库时发生的情景,两个库都给你的核心类添加了一个名字相似但实现不同的方法。什么行为是期望的?有经验的动态语言使用者可能之前就见过这个问题,并且可能听说它被称为 _“猴子补丁(monkey patching,译注:在不改变原始代码的情况下扩展或修改动态语言运行时代码的方法)”_。若能静态编译代码库中的部分代码 - 那些不需要动态特性的代码 - 保护了你不受猴子补丁的影响,因为静态编译后的代码不会经过 Groovy 的动态运行系统。尽管语言的动态运行时方面不允许出现在静态编译环境中,但所有常用的 AST 转换机制还会像以前一样工作良好,因为多数 AST 转换机制也是在编译时施展它们的魔法。

说到性能,Groovy 的静态编译代码通常会或多或少跟 javac 的一样快。在开发团队使用的一些微基准测试中,有些情况下性能相同,而有时则可能稍慢。

在以前,由于 Java 和 Groovy 透明无缝的集成,我们过去常建议开发者优化 Java 的 hotspot 例程以获得进一步改进性能,但是现在,有了这个静态编译选择,情况变了,那些想完全用 Groovy 开发项目的人们也能这样做了。

Java 7 和 JDK 7 主题

Groovy 编程语言的语法其实来自于 Java 语法本身,但很明显,Groovy 提供了额外漂亮的便捷方法让开发者生产力更高。让 Java 开发者熟悉的语法一直以来都是这个项目的重要卖点,并且被广泛接纳,这得益于平坦的学习曲线。我们当然也期望 Groovy 用户和新人也能从 Java 7 增加的 _“Project Coin”_ 所提供的一些语法改进中受益。

除了语法,JDK 7 还为它的 API 带来了一些有趣的新事物,这是长久以来的第一次,它甚至添加了一个被称为 _“invoke dynamic”_ 的字节码指令,它旨在让实现者更容易地开发他们的动态语言和获得更高的性能。

Project Coin 语法增强

从第一天开始(这要从 2003 年说起!),Groovy 就拥有几处建立在 Java 之上的语法增强和特性。例如,人们可以想到的是闭包,以及switch/case语句中可使用的不仅限于离散值,而 Java 7 中只是多了能使用多个 String。所以一些 Project Coin 语法增强,比如 switch 中的多个 String,已经在 Groovy 中了。然而,有些增强是新的,如二进制字面量、数字字面量中的下划线或者多 catch 块,Groovy 2 都支持。唯一漏掉的 Project Coin 增强是"try with resources"结构,对于它,Groovy 通过 Groovy Development Kit 丰富的 API 提供了多个替代解决方案。

二进制字面量

在 Java 6 及之前版本,以及 Groovy 中,数字可以表示成十进制、八进制和十六进制,而在 Java 7 和 Groovy 2 中,你可以使用以“0b”做前缀的二进制符号:

<b>int</b> x = 0b10101111<br></br><b>assert</b> x == 175<p><b>byte</b> aByte = 0b00100001</p><br></br><b>assert</b> aByte == 33<p><b>int</b> anInt = 0b1010000101000101</p><br></br><b>assert</b> anInt == 41285## 数字字面量中的下划线

当写长变量数字时,很难用肉眼分辨出一些数字是如何分组聚合在一起的,例如千位分组,单词等等。通过允许在数字字面量中放置下划线,就很容易区分这些分组了:

<b>long</b> creditCardNumber = 1234_5678_9012_3456L<br></br><b>long</b> socialSecurityNumbers = 999_99_9999L<br></br><b>double</b> monetaryAmount = 12_345_132.12<br></br><b>long</b> hexBytes = 0xFF_EC_DE_5E<br></br><b>long</b> hexWords = 0xFFEC_DE5E<br></br><b>long</b> maxLong = 0x7fff_ffff_ffff_ffffL<br></br><b>long</b> alsoMaxLong = 9_223_372_036_854_775_807L<br></br><b>long</b> bytes = 0b11010010_01101001_10010100_10010010## 多 catch 块

当捕获到异常时,我们通常会复制两个或更多的异常块,因为我们想用同样的方式处理它们。解决方法是,要么在它自己的方法中分离出通用的内容,或者一种更丑陋的方式就是通过捕获Exception(或者更糟的Throwable)完成一个捕获所有异常的方法。用多 catch 块,我们能定义要用一个 catch 块捕获和处理的多种异常:

<b>try</b> {<br></br>    <i>/* ... */</i><br></br>} <b>catch</b>(IOException | NullPointerException e) {<br></br>    <i>/* 一个代码块处理 2 个异常 */</i><br></br>}## Invoke Dynamic 的支持

正如本文之前提到的,JDK 7 带来了一个被称为 _“invoke dynamic"的新字节码指令以及相关的 API。其目的是帮助动态语言实现者在 Java 平台之上打造自己的语言,实现手段则是:简化动态方法的调用路径,定义可缓存动态方法的"call site”,作为方法指针的"method handles",存储类对象中各种元数据的"class values"_,以及其他一些内容。不过事先提醒,尽管承诺性能改进,但"invoke dynamic"在 JVM 内部还没有完全优化,也并不总能提供最好的性能,但随着一步步的更新,优化就会到来。

Groovy 带来了它自己的实现技术,用“call site 缓存”加速方法的选择和调用,用元类注册库存储元类(类的等价动态运行时),执行跟 Java 一样快的原生原始计算(native primitive calculation),等等。但随着“invoke dynamic”的问世,我们将重新把 Groovy 的实现置于这些 API 和 JVM 字节码指令之上,以获得性能的改进和简化我们的代码库。

如果有幸运行 JDK 7,你就能使用已经编译进"invoke dynamic"支持的 Groovy JAR 的新版本。很容易辨认那些 JAR,因为它们名字都含有"-indy"区分。

启用 invoke dynamic 支持

然而,要想利用"invoke dynamic",光用"indy"JAR 编译你的 Groovy 代码还不够。鉴于此,使用“groovyc”编译器或者“groovy”命令时,你必须使用–indy 标记。这也就意味着,就算用的是 indy JAR,你仍可以面向 JDK 5 或 6 进行编译。

同样的,如果你正在使用 groovyc Ant task 编译你的项目,你还可以指定 indy 属性:

复制代码
...<br></br><taskdef name="groovyc"<br></br>        classname="org.codehaus.groovy.ant.Groovyc"<br></br>        classpathref="cp"/><br></br>...<br></br><groovyc srcdir="${srcDir}" destdir="${destDir}" <b>indy=</b>"<b>true</b>"><br></br>    <classpath><br></br>...<br></br>    </classpath><br></br></groovyc><br></br>...

Groovy Eclipse Maven 编译器插件还没有更新包含 Groovy 2.0 支持,但也快了。对于 GMaven 插件的用户,尽管已经可以配置插件使用 Groovy 2.0,目前还没有支持 invoke dynamic 的标志位。同样,GMaven 很快也会有这方面的更新。

当在 Java 应用中集成 Groovy 时,用GroovyShell,你还可以通过给GroovyShell构造函数传递一个CompilerConfiguration实例来激活 invoke dynamic 支持,在GroovyShell上可以访问和设置优化选项:

复制代码
CompilerConfiguration config = <b>new</b> CompilerConfiguration();<br></br><b>config.getOptimizationOptions().put(</b>"<b>indy</b>"<b>, true);</b><br></br><b>config.getOptimizationOptions().put(</b>"<b>int</b>"<b>, false);</b><br></br>GroovyShell shell = <b>new</b> GroovyShell(config);

由于 invokedynamic 被期望成能够完全替代动态方法分发,禁用那些为了优化边缘情况而生成额外二进制码的原始优化(primitive optimizations)是有必要的。即使在某些情况下它比激活原始优化慢,JVM 的未来版本将会对 JIT 有所改进,它将有能力内联(inlining)多数调用并去除那些没必要的装箱(boxing)。

性能改进承诺

在我们的测试中,我们注意到有些领域取得了有趣的性能改进,而其他程序比没使用 invoke dynamic 支持的运行慢。然而,Groovy 团队在 Groovy 2.1 的 pipeline 中取得了进一步的性能改进,但我们注意到 JVM 还没有微调,全面优化仍然有很长的路要走。但所幸,即将到来的 JDK 7 的更新(尤其是更新 8)应该已经包含了这样的改进,这样的情况必将改善。此外,随着 invoke dynamic 被用于 JDK 8 的 Lambdas 实现,我们可以保证未来会有更大的改进。

模块性更佳的 Groovy

我们将通过模块化介绍,结束这次 Groovy 2.0 新特性之旅。就像 Java,Groovy 不只是一门语言,它还是服务于多种用途的 API 集合:模板、Swing UI 构建、Ant 脚本、JMX 集成、SQL 访问、servlet 服务等。Groovy 的发布版就是把所有这些特性和 API 打成一个大的 JAR。但不是所有人在自己的应用里总是需要所有内容:如果正在写 Web 应用,你会对模板引擎和 Servlet 感兴趣,但是如果正在做一个富桌面客户端程序,你可能仅需要 Swing 构建器。

Groovy 的模块

因此,本版本模块化的第一个目标就是将原始的 Groovy JAR 真真切切的划分成更小的模块、更小的 JAR。核心的 Groovy JAR 文件现在缩小了一半,我们有如下可用的特性模块:

  • Ant:为自动化管理任务提供脚本化的 Ant 任务;
  • BSF:用老的 Apache Bean 脚本框架为你的 Java 应用集成 Groovy;
  • Console:包含 Groovy Swing console 的模块;
  • GroovyDoc:文档化你的 Groovy 和 Java 类;
  • Groovysh:与 Groovysh 命令行 shell 相关的模块;
  • JMX:暴露和消费 JMX bean;
  • JSON:生产和消费 JSON
  • JSR-223:使用 JDK 6+ javax.scripting API 将 Groovy 集成到你的 Java 应用中
  • Servlet:编写和服务 Groovy script servlet 和 template
  • SQL:查询关系数据库;
  • Swing:构建 Swing UI;
  • Templates:使用模板引擎
  • Test:某些测试支持,如 GroovyTestCase、mocking 等等;
  • TestNG:用 Groovy 写 TestNG 测试;
  • XML:产生和消费 XML 文档。

对于 Groovy 2,你现在可以只挑选感兴趣的模块,而不用把所有内容都带入到 classpath 中。但我们仍提供包含所有内容的“完整”JAR,假如你不想只是为了节省一点空间就要处理复杂的依赖关系的话。我们还为运行在 JDK7 上的代码提供了用“invoke dynamic”支持选项编译后的 JAR 文件。

扩展模块

让 Groovy 变得更模块化的工作也产生了一个有趣的新特性:扩展模块(extension module)。通过将 Groovy 分裂成更小模块,方便模块扩展方法的机制已经建立。由此,扩展模块可以给其他类,包括来自 JDK 或第三方库的类,提供实例和静态方法。Groovy 用这种机制修饰了来自 JDK 的类,给诸如String、File、流以及其他更多的类添加了新的有用方法 - 例如,URL 上的getText()方法,允许你通过 HTTP get 获得远程 URL 的内容。还需要注意的是,静态类型检查器和编译器也知道你模块中的这些扩展方法。但先看看如何给现有类型添加新的方法。

添加实例方法

要给现有类型添加新的方法,你必须创建一个包含这些方法的帮助类。在这个帮助类中,所有的扩展方法其实都是public的(这在 Groovy 中是缺省的,但若用 Java 实现,就需要标出)和static的(尽管它们将在类的实例中可用)。它们接受的第一个参数其实总是要在上面调用扩展方法的实例。余下参数将在调用时被传入。这跟 Groovy 的 Category 使用的是一样的惯例。

假定我们要给String添加一个greets()方法, 它向作为参数传入的人名问好,所以你可以像下面这样写:

<b>assert</b> "Guillaume".greets("Paul") == "Hi Paul, I'm Guillaume"要实现它,你要创建一个含有这个扩展方法的帮助类,如:

<b>package</b> com.acme<p><b>class</b> MyExtension {</p><br></br>    <b>static</b> String greets(String self, String name) {<br></br>        "Hi ${name}, I'm ${self}"<br></br>    }<br></br>}## 添加静态方法

对于静态扩展方法,用同样的机制和惯例。现在我们给 Random 添加一个静态方法,获得两个值之间的一个随机整数,你可以按照这个类来处理:

<b>package</b> com.acme<p><b>class</b> MyStaticExtension {</p><br></br>    <b>static</b> String between(Random selfType, <b>int</b> start, <b>int</b> end) {<br></br>        <b>new</b> Random().nextInt(end - start + 1) + start<br></br>    }<br></br>}这样,你可以用如下方式使用这个扩展方法:

复制代码
Random.between(3, 4)

扩展模块描述符

一旦编写好了包含扩展方法的帮助类(用 Groovy 或 Java),你需要为模块创建描述符。你必须在模块文件夹的META-INF/services目录下创建一个名为org.codehaus.groovy.runtime.ExtensionModule的文件。可以定义四个基本属性,告诉 Groovy 运行时模块的名字和版本,以及用逗号隔开的类名列表,这些类就是为扩展方法写的帮助类。如下是我们最终的模块描述:

复制代码
moduleName = MyExtension<br></br>moduleVersion = 1.0<br></br>extensionClasses = com.acme.MyExtension<br></br>staticExtensionClasses = com.acme.MyStaticExtension

一旦 Classpath 中有了这个扩展模块描述符,现在就能在代码中使用这些扩展方法了,不需要 import 或者其他动作,因为这些扩展方法是自动注册的。

获取扩展

在脚本中使用 @Grab 注解可以从类似 Maven Central 这样的 Maven 库中获取依赖。此外,使用 @GrabResolver 注解,你还能为依赖指定自己的位置。如果你正通过这种机制“获取”一个扩展模块,扩展方法也会被自动安装。理想情况下,出于一致性考虑,模块名字和版本应该跟制品的 id 和版本关联。

总结

Groovy 在 Java 开发人员中很流行,并为他们的应用提供了成熟的平台和生态系统。但我们并未满足于现状,Groovy 开发团队会一如既往继续提高语言和它的 API,帮助用户在 Java 平台上提高他们的生产率。

Groovy 2.0 致力于三个关键主题:

  • 更高性能:借助JDK 7 Invoke Dynamic 的支持,它会为那些有幸在生产环境中已使用 JDK7 的开发者提高 Groovy 的速度;对于使用 JDK 5 及以上版本的所有人,则要借助静态编译,特别是那些打算放弃一些动态特性避免"猴子补丁"并想获得与 Java 相同速度的人而言。
  • 对于 Java 更友好Java 7 Project Coin 增强的支持让 Groovy 和 Java 一如既往的表现为亲密的语法表兄弟,并且在静态类型检查器上给予将 Groovy 作为 Java 脚本语言的开发者跟 javac 编译器相同的反馈和类型安全。
  • 模块化更佳:借助新的模块化级别,Groovy 开启了更小交付包的大门,例如,集成进 Android 上的移动应用,并允许 Groovy API 发展和融入新的版本和新的扩展模块,同时还允许用户为现有类型贡献新的方法。

关于作者

作为 SpringSource 的 Groovy 开发主管,VMware 部门经理, Guillaume Laforge 是官方的 Groovy 项目经理,领导 Codehaus 下的 Groovy 动态语言项目。

他发起创建 Grails Web 应用框架,建立了 Gaelyk 项目,一个用 Groovy 为 Google App Engine 开发应用的轻量级的工具。他还是频繁在 JavaOne、GR8Conf、SpringOne2GX、QCon 和 Devoxx 等大会上介绍 Groovy、Grails、Gaelyk、领域建模语言的会议发言人。

Guillaume 也是法国 French Java/OSS/IT 播客 LesCastCodeurs 的创始成员之一。

原文链接: What’s new in Groovy 2.0?


感谢胡键对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2012 年 10 月 19 日 00:006367

评论

发布
暂无评论
发现更多内容

week13 总结

雪涛公子

架构师课程第十三周总结

dongge

详解 Python 的二元算术运算,为什么说减法只是语法糖?

Python猫

Python 编程 翻译

Linux Shell编程

yuanhang

Shell

架构师训练营Week13作业

Frank Zeng

极客大学架构师训练营

打破Scrum的五个误区(译)

Bruce Talk

Scrum 敏捷开发 Agile

【架构师训练营】第 13周作业

花生无翼

架构师训练营 week13 - 学习总结

devfan

普通上班族如何快速买房买车,一个程序员摸索的实操经验分享

陆陆通通

副业 程序员赚钱 认知

Go 云原生应用实战系列(二)

田晓亮

go 微服务 云原生

Week13 学习总结

赵龙

week13 homework

burner

架构师训练营第十三周作业

叮叮董董

Centos7 IP、名字、防火墙配置

yuanhang

centos7 防火墙 静态IP

架构师训练营Week13总结

Frank Zeng

极客大学架构师训练营

Week 13 作业

鱼_XueTr

大数据架构&数据应用/分析&机器学习(二)

dony.zhang

flink spark 学习 Storm

【第十三周】命题作业——Google 搜索排序

赵龙

使用Typora+PicGo配置Gitee图床

清菡

图床

第十三周

Acker飏

你所在的行业,常用的数据分析指标有哪些?

李朋

初露锋芒的AI战斗机,打开AI军备竞赛的潘多拉盒子

脑极体

为微服务建一个简约而不简单的配置中心

架构师修行之路

微服务 etcd 配置中心

架构师训练营 week13

devfan

架构师训练营第十三章作业

吴吴

甲方日常 11

句子

工作 随笔杂谈 日常

大数据解答(二)

dony.zhang

数据分析

绝了!大厂20个企业级实战项目,靠它成功定级了阿里P7

周老师

Java 编程 程序员 架构 面试

第十三周作业

Linuxer

week13 作业

雪涛公子

Week13

一叶知秋

技术为帆,纵横四海- Lazada技术东南亚探索和成长之旅

技术为帆,纵横四海- Lazada技术东南亚探索和成长之旅

Groovy 2.0新特性-InfoQ