Groovy 1.5 的新特性

阅读数:2643 2008 年 1 月 15 日 23:12

Groovy ,针对 JVM 的类 Java 动态语言,如陈年好酒一样成熟了。在 2007 年 1 月成功地发布了 Groovy 1.0 之后,下一个主要的里程碑 1.5 版已经发布。在 1.5 版中有一些有趣而新颖的地方,我们会在这篇文章中考察这些特性。语言主要增强了对于 Java 5 特征的支持,包括注解、泛型和枚举,这使得 Groovy 成为对于JVM 完全支持的框架的唯一候选动态语言,框架包括 Spring,Hibernate,JPA,Goole Guice 或者 TestNG。除了新的 Java 5 特性,Groovy 还在语言中增加了新的语法增强,以及更强大的动态特性定制,一个基于 steroids 的 Swing UI 构建器以及改进的工具支持。

为什么一个更加 groovy 的 Groovy 是很重要的

Groovy 的关键卖点始终是它与 Java 的无缝集成。你能够很容易地把 Groovy 和 Java 的类混合搭配:你可以让一个 Java 类实现一个 Groovy 接口,然后让一个 Groovy 类继承那个 Java 类,或者相反。不幸的是,绝大多数其他的候选的 JVM 语言不能让你无缝的在两种不同的语言之间交换类。因此,如果你希望为工作使用最好的语言而不放弃优美的类的层次结构,你没有太多的选择,而 Groovy 使得你可以自由的将两种语言以几乎透明的方式集成在一起。

Groovy 与 Java 共享同样的库,同样的对象模型,同样的线程模型,同样的安全模型。在某种意义上,你可以认为 Groovy 是你的 Java 项目的一个实现细节,而不必忍受阻抗失配问题

Groovy 就是 Java,而且 Groovy 使得 Java 更 groovy 了。与其他语言相比,Groovy 对于 Java 开发者无疑提供了最平滑的学习曲线,这得益于两者非常相似的语法。

需要牢记的是 Groovy 产生的是正常的 Java 字节码而且使用普通的 JDK 库,所以你不需要学习全部的新的 API 而且不需要复杂的集成机制:极其方便,Groovy 和 Java 是可以相互交换的。附加的好处是你可以保护对你的 Java 开发人员 Java 技巧方面的投资,或者是昂贵的应用服务器,或者第三方的或者公司自己开发的库,你可以在 Groovy 中毫无问题地重用他们。

其他不支持强类型的候选语言,在调用 JDK、第三方库或者公司自己的库的时候,由于它们不能辨别同一方法的某一多态变种,所以始终不能调用所有的 Java 方法。当你选择一种语言来提高你的生产率或者使你的代码可读性更强的时候,如果你需要调用其他 Java 类,你必须非常谨慎的选择语言,因为可能会碰到很多麻烦。

今天,所有主要的企业框架都需要使用注解、枚举或者泛型这样的语言特性来充分提高它们的效率。幸运的是,开发者使用 Groovy1.5 的话就可以在他们的项目中使用所有的 Java 5 特性并因此而获益。让我们看看在 Groovy 中如何使用注解,枚举和泛型。

Java 5 增加的部分

Groovy 编译器始终产生与以前的 Java VM 兼容的 Java 字节码,但是由于 Groovy 使用了 JDK1.4 的核心库,所以 Groovy 依赖于 JDK1.4。然而,对于这些 Java 5 中增加的部分,肯定需要使用 Java 5 的字节码。例如,产生的类中也许包含代表着运行时保留策略注解的字节码信息。所以,虽然 Groovy1.5 能够在 JDK1.4 上运行,但是某些 Groovy 的特征只能在 JDK1.5 上使用 —— 出现这种情况时,本文会作出声明。

可变的参数

在 Java 5 中创建了省略号表示法,代表方法的参数是可变长度的。通过三个小圆点,Java 允许用户在一个方法的末端输入相同类型的任意数量的参数 —— 实际上,可变长度参数(vararg)就是一个那种类型的元素的数组。可变长度参数在 Groovy 1.0 中已经出现了 —— 现在仍然可以在 JDK1.4 运行时环境下工作,1.0 足以向你展示如何来使用他们了。基本上,只要当一个方法的最后一个参数是一个对象数组,或者是一个有三个点的参数,你就可以向这个方法传入多重参数。

第一个例子介绍了在 Groovy 中用省略号来使用可变长度变量的方法:

int sum(int... someInts) {

def total = 0

for (int i = 0; i < someInts.size(); i++)

total += someInts[i]

return total

}

assert sum(1) == 1



assert sum(1, 2) == 3

assert sum(1, 2, 3) == 6

这个例子中所用的断言显示了我们如何传入任意多的 int 类型的参数。还有一个有趣的地方,为了更好的兼容 Java 语法,Java 中经典的循环方式也加入了 Groovy 中 —— 尽管在 groovy 中更有 groovy 特色的循环是用 in 关键字,同样可以透明地遍历各种各样的数组或者集合类型。

请注意使用一个数组作为最后一个参数同样可以支持可变长度变量,就像下面这样声明方法:

int sum(int[] someInts) { /* */ }

这个代码片断是非常无聊的。很明显有很多更有表现力的方式来计算一个总和。例如,如果你有一个数字的列表,你可以在一行代码中计算他们的总和:

assert [1, 2, 3].sum() == 6

在 Groovy 中可变长度变量不需要 JDK 5 作为基本的 Java 运行时环境,在下面的章节中我们要介绍的注解则需要 JDK 5。

注解

正如在 JBoss Seam 的文档中所介绍的那样,Seam 支持使用 Groovy 来写 Seam 的实体,控制器和组件,类似@Entity,@Id,@Override以及其他的注解可以用来修饰你的 bean:

@Entity

@Name("hotel")

class Hotel implements Serializable

{

@Id @GeneratedValue

Long id

@Length(max=50) @NotNull



String name

@Length(max=100) @NotNull



String address

@Length(max=40) @NotNull



String city

@Length(min=2, max=10) @NotNull



String state

@Length(min=4, max=6) @NotNull



String zip

@Length(min=2, max=40) @NotNull



String country

@Column(precision=6, scale=2)



BigDecimal price

@Override



String toString() {

return "Hotel(${name}, ${address}, ${city}, ${zip})"

}

}

Hotel 实体用 @Entity 注解来标识,用 @Name 给了它一个名字。可以向你的注解传递不同的参数,例如在 @Length 注解约束中,为了做有效性检查可以给注解设置不同的上界和下界。在实例中你还会注意到Groovy 的属性:getter 方法和 setter 方法都到哪里去了?公有或者私有的修饰符在哪里?你不必等待 Java 7 或者 Java 8 来获得属性!在 Groovy 中,按照惯例,定义一个属性非常简单:String country:这样就会自动生成一个私有的 country 成员变量,同时生成一个公有的 getter 和 setter 方法。你的代码自然而然的变得简洁而易读

在 Groovy 中,注解可以象在 Java 中一样用在类、成员变量、方法和方法参数上。但是,有两个很容易犯错误的地方需要小心。第一,你可以在 Groovy 中用注解,可是你不能定义它们 —— 然而,在一个快要到来的 Groovy 版本中将可以定义注解。第二,虽然 Groovy 的语法几乎与 Java 的语法 100% 相同,但是在注解中传入一个数组作为参数时还是有一点点不同:Groovy 不是用圆括号来括起元素,而是需要使用方括号,目的是为了提供更一致的语法 —— 在 Groovy 中列表和数组都用方括号来括起他们的元素。

通过 Groovy1.5 中的注解,你可以在 Groovy 中方便地为 JPA 或者 Hibernate 定义你的的带注解的 bean

http://www.curious-creature.org/2007/03/25/persistence-made-easy-with-groovy-and-jpa/ ),在你的 Spring 服务上增加一个@Transactional 注解,使用 TestNG 和 Fest 来测试你的 Swing UI( http://www.jroller.com/aalmiray/entry/testing_groovy_uis_with_fest )。在 Groovy 项目中你可以使用所有支持注解的有用而强大的企业框架。

枚举

当你需要一组固定数量的相同类型的常量时,枚举是很方便的。例如你需要一种干净的方式来为日期定义常量而不借助使用整数常量,那么枚举是你的好帮手。下面的片断显示了如何定义一星期中的日子:

enum Day {

SUNDAY, MONDAY, TUESDAY, WEDNESDAY,

THURSDAY, FRIDAY, SATURDAY

}

一旦你定义了你的枚举,你可以在 Java 中以通常的记法Day.MONDAY来使用它,还可以使用枚举来润色你的 switch/case 语句:

def today = Day.SATURDAY

switch (today) {

// Saturday or Sunday

case [Day.SATURDAY, Day.SUNDAY]:

println "Weekends are cool"

break // a day between Monday and Friday



case Day.MONDAY..Day.FRIDAY:

println "Boring work day"

breakdefault:



println "Are you sure this is a valid day?"

}

请注意 Groovy 的 switch 语句比类似 C 风格语言的 switch 语句要强大一些,在 Groovy 中可以在 switch 和 case 语句使用任何类型的对象。不用为每一个枚举值罗列七个不同的 case 语句块,你可以在列表或者 ranges(Groovy 集合类的一种类型)中重新分组 case 语句:当值出现在列表或者 range 中,case 将为真而且会执行它关联的命令。

受到 Java 教程的启示,这里是一个更复杂的关于天文学的例子,向你展示了在枚举中如何包含属性,构造器和方法:

enum Planet {

MERCURY (3.303e+23, 2.4397e6),

VENUS (4.869e+24, 6.0518e6),

EARTH (5.976e+24, 6.37814e6),

MARS (6.421e+23, 3.3972e6),

JUPITER (1.9e+27, 7.1492e7),

SATURN (5.688e+26, 6.0268e7),

URANUS (8.686e+25, 2.5559e7),

NEPTUNE (1.024e+26, 2.4746e7)

double mass



double radius

Planet(double mass, double radius) {



this.mass = mass;

this.radius = radius;

}

void printMe() {



println "${name()} has a mass of ${mass} " +

"and a radius of ${radius}"

}

}

Planet.EARTH.printMe()

与注解一样,由于产生了 Java 5 的字节码,Groovy 中的枚举需要 JDK 5+ 的环境才能运行,

静态导入

在前面关于枚举的例子中,我们始终需要在枚举值的前面加上它的父枚举类,但是通过静态导入(可以在 JDK1.4 运行时环境上工作)我们可以去掉 Planet 前缀,从而节省一些字符。

import static Planet.*

SATURN.printMe()

这样就不再需要 Planet 前缀。当然,静态导入不仅仅对枚举有效,对其他类和静态成员变量同样有效。我们不妨作些数学计算。

import static java.lang.Math.*

assert sin(PI / 6) + cos(PI / 3) == 1

java.lang.Math 的静态方法和静态常量都被静态导入了,这样使得表达式更加简明。但是如果 sine 和 cosine 的缩写不便于你阅读,那么你可以使用 Groovy 中的 as 关键字来做别名:

import static java.lang.Math.PI

import static java.lang.Math.sin as sine

import static java.lang.Math.cos as cosine

assert sine(PI / 6) + cosine(PI / 3) == 1

别名不仅仅用于静态导入,也可以用于正常的导入,是很有用的方法。例如在很多框架中有名字非常长的类,可以使用别名来增加快捷记法,或者重命名名字不太直观的方法或者常量,或者重命名与你的命名约定标准不一致的方法或常量。

泛型

在 Java 5 中有争议的特性:泛型,也出现在 Groovy 1.5 的最新版本中。毕竟,开始的时候可能觉得在一个动态语言中加入更多类型信息是多余的。Java 开发人员通常相信因为类型擦除(为了向后兼容 Java 以前的版本)使得在类的字节码中没有保留代表泛型的类型信息。然而,这是错误的看法,通过反射 API,你可以内省一个类从而发现它的成员变量类型或者它的有泛型详细信息的方法参数类型。

例如,当你声明了类型为List的成员变量时,这个信息是在字节码的某个地方以某种元信息的方式保存的,尽管这个成员变量确实仅仅是List类型的。这种反射信息被诸如 JPA 或者 Hibernate 这样的企业框架所使用,将一个元素的集合中的实体关联到代表这些元素的类型的实体。

为了实践这些理论,让我们检查泛型信息是否保存在类的成员变量中。

class Talk {

String title

}

class Speaker {



String name

List talks = []

}

def me = new Speaker(



name: 'Guillaume Laforge',

talks: [

new Talk(title: 'Groovy'),

new Talk(title: 'Grails')

])

def talksField = me.class.getDeclaredField('talks')



assert talksField.genericType.toString() ==

 'java.util.Listt'
我们定义了两个类:一个在会议上给出 Talk 的 Speaker 类。在 Speaker 类中,talks 属性的类型是List。然后,我们创建了一个 Speaker 实例,用两个优美的捷径来初始化 name 和 talks 属性,并创建了一个 Talk 实例的列表。当初始化代码就绪后,我们取得代表 talks 的成员变量,然后检查泛型信息是否正确:正确!talks是一个List,但是它是一个TalkList



共变的返回类型

在 Java 5 中,如果你在一个子类中有一个方法,其名称与参数类型与父类中的方法相同,但是返回值是父类方法的返回值的子类,那么我们可以覆盖父类的方法。在 Groovy1.0 中,不支持共变的返回类型。但是在 Groovy1.5 中,你可以使用共变返回类型。而且,如果你试图覆盖一个方法而返回类型不是父类方法的返回类型的子类,将抛出一个编译错误。共变的返回类型对于参数化的类型同样有效。

除了因为支持 Java 5 的特性而给 Groovy 语言带来了一些增强外,Groovy1.5 还引入了其他一些语法的增强,我们在下面的章节中会探索这些部分。

增加的语法

Elvis 操作符

Java 5 的特性除了带给 Groovy 注解,泛型和枚举,还增加了一个新操作符—— ?:,Elivis 操作符。当你看这个操作符的时候,你很容易猜测为什么会这样命名 —— 如果不是,可以根据 Smiley 来思考。这个新操作符实际上是一个三目操作符的便捷记法。你是否经常使用三目操作符来改变一个变量的值?如果它是 null 那么给它分配一个缺省值。在 Java 中典型的情况是这样的:

String name = "Guillaume";

String displayName = name != null ? name : "Unknown";

在 Groovy 中,由于语言本身可以按需“强制”类型转换到布尔值(例如在 if 或者 while 构造中条件表达式需要为布尔值),在这个语句中,我们可以忽略和 null 的比较,因为当一个 String 是 null 的时候,它被强制转换为 false,所以在 Groovy 中语句会变为:

String name = "Guillaume"

String displayName = name ? name : "Unknown"

然而,你仍然会注意到 name 变量的重复,这破坏了 DRY 原则(不要重复你自己 Don't Repeat Yourself)。由于这个构造非常普遍,所以引入了 Elvis 操作符来简化这些重复的现象,语句变成:

String name = "Guillaume" 

String displayName = name ?: "Unknown"

name 变量的第二次出现被简单的忽略了,三目操作符不再是三目的了,缩短为这种更简明的形式。

还有一点值得注意的是这个新的构造没有副作用,由于第一个元素(这里是 name)不会象在三目操作符中那样被估值两次,所以不需要引入一个中间的临时变量来保持三目操作符中第一个元素的第一次估值。

经典的循环

虽然 Groovy 严格地来说不是 100%的 Java 的超集,但是在每一个 Groovy 的新版本中,其语法都更接近 Java 的语法,在 Groovy 中越来越多的 Java 代码是有效的。这种兼容性的好处是当你开始用 Groovy 工作时,你可以拷贝并粘贴 Java 代码到你的 Groovy 类中,它们会如你所愿地工作。然后,随着时间的推移你学习了 Groovy 语言,你可以扔掉那些从 Java 拷贝来的在 Groovy 中不地道的代码,使用 GStrings(内插字符串),或者闭包等等。Groovy 为 Java 开发者提供了一个非常平滑的学习曲线。

然而,Groovy 中有一处忽略了 Java 语法的兼容性,实际上 Groovy 中不允许使用从 Java 语言的 C 背景继承而来的经典的循环语法。最初,Groovy 开发者认为经典的循环语法不是最好的,他们更喜欢使用可读性更好的 for/in 构造。但是由于 Groovy 用户经常要求 Groovy 包含这个旧的循环构造,所以 Groovy 团队决定支持它。

在 Groovy 1.5 中,你可以选择 Groovy 的 for/in 构造,或者经典的 for 循环构造:

for (i in 0..9)

println i

for (int i = 0; i < 10; i++)



println i

最终,这也许只是品味不同,Groovy 的熟手用户通常更喜欢 for/in 循环这样更加简明的语法。

没有圆括号的命名参数

由于易适应且简明的语法,以及高级的动态能力,Groovy 是实现内部领域特定语言(Domain-Specific Languages)的理想选择。当你希望在业务问题专家和开发者之间共享一种公共的比喻说法的时候,你可以借 Groovy 之力来创建一个专用的商业语言,用该语言为你的应用的关键概念和商业规则建模。这些 DSL 的一个重要方面是使得代码非常可读,而且让非技术人员更容易写代码。为了更进一步实现这个目标,Groovy 的语法做了通融,允许我们使用没有圆括号括起来的命名参数。

首先,在 Groovy 中命名参数看起来是这样的:

fund.compare(to: benchmarkFund, in: euros)

compare(fund: someFund, to: benchmark, in: euros)

通过向数字加入新的属性 —— 这在 Groovy 中是可能的,但是超出了这篇文章的范围 —— 我们可以写出像这样的代码:

monster.move(left: 3.meters, at: 5.mph)

现在通过忽略圆括号,代码变得更清晰了:

fund.compare to: benchmarkFund, in: euros

compare fund: someFund, to: benchmark, in: euros

monster.move left: 3.meters, at: 5.mph

显然,这没有很大的区别,但是每个语句变得更接近浅白的英语句子,而且在宿主语言中删除了通常冗余的技术代码。Groovy 语言这个小小的增强给予了商业 DSL 设计人员更多的选择。

改善的工具支持

当 Groovy 还不成熟的时候,一个常见的弱点是缺乏好的工具支持:工具系列和 IDE 支持都不到位。幸运的是,随着 Groovy 和 Grails web 框架的成熟和成功,这种状况得到了改变。

“联合”编译器的介绍

Groovy 以它与 Java 的透明而且无缝的集成而闻名。但是这不仅仅意味着在 Groovy 脚本中可以调用 Java 方法,不,两个语言之间的集成远不止于此。例如,一个 Groovy 类继承一个 Java 类,而该 Java 类实现一个 Groovy 接口是完全可能的,反之亦然。不幸的是,其他候选语言不支持这样做。然而,到目前为止,当把 Groovy 和 Java 混合起来使用的时候,你在编译时要小心选择正确的编译顺序,如果两个语言中出现循环依赖,那么你也许会碰到一个“鸡与蛋”的问题。幸运的是在 Groovy 1.5 中这不再是问题,谢谢获奖的 Java IDE IntelliJ IDEA 的创建者 JetBrains 的一个贡献,你可以使用一个“联合”编译器将 Groovy 和 Java 代码放在一起一次编译而不必考虑类之间的依赖关系。

如果你希望在命令行使用联合编译器,你可以像通常那样调用 groovyc 命令,但是使用 -j 参数来进行联合编译:

groovyc *.groovy *.java -j -Jsource=1.4 -Jtarget=1.4

为了向基本的 javac 命令传递参数,你可以用 J 作为参数的前缀。你还可以在你的 Ant 或者 Maven 构建文件中使用联合编译器执行 Ant 任务:

<taskdef name="groovyc"
             classname="org.codehaus.groovy.ant.Groovyc"
             classpathref="my.classpath"/>

<groovyc
       srcdir="${mainSourceDirectory}"
       destdir="${mainClassesDirectory}"
       classpathref="my.classpath"
       jointCompilationOptions="-j -Jsource=1.4 -Jtarget=1.4" />

Groovy 的 Maven 插件

对于 Maven 用户,在 Codehaus 有一个全特性的 Maven 插件项目允许你构建自己的 Java/Groovy 应用:编译你的 Groovy 和 Java 代码,从 JavaDoc 标签生成文档,甚至允许你在 Groovy 中开发自己的 Maven 插件。还有一个 Maven 的原型可以更迅速的引导你的 Groovy 项目。要得到更多信息,你可以参考插件的文档: http://mojo.codehaus.org/groovy/index.html

GroovyDoc 文档工具

作为一个 Java 开发人员,你习惯于通过你的类,接口,成员变量或者方法的注释中的 JavaDoc 标签来生成代码文档。在 Groovy 中,你仍然可以在你的注释中使用这样的标签,使用一个叫做 GroovyDoc 的工具为你所有的 Groovy 类生成与 JavaDoc 同样的文档。

这里有一个 Ant 任务,你可以定义并用它来产生文档:

<taskdef name="groovydoc"
       classname="org.codehaus.groovy.ant.Groovydoc">
       <classpath>
            <path path="${mainClassesDirectory}"/>
            <path refid="compilePath"/>
       </classpath>
    </taskdef>

<groovydoc
       destdir="${docsDirectory}/gapi"
       sourcepath="${mainSourceDirectory}"
       packagenames="**.*" use="true"
       windowtitle="Groovydoc" private="false"/>

新的交互性 shell 和 Swing 控制台

Groovy 的发行版本总是包含两个不同的 shell:一个命令行 shell 和一个 Swing 控制台。命令行 shell,Groovysh,就其与用户的交互性而言从来都不是很友好:当你希望执行一个语句的时候,你不得不在每个语句后面键入“go”或者“execute”,这样才能执行。为了某些快速的原型开发或者试用一些新的 API,每次都键入“go”是非常累赘的。在 Groovy 1.5 中情况变化了,有了新的交互式的 shell。不再需要键入“go”。

这个新的 shell 有几个增强的特性,例如使用了提供 ANSI 着色的 JLine 库,tab 命令补全,行编辑能力。你可以与不同的脚本缓冲器工作,记住已经导入的类,装载现存的脚本,将当前脚本保存到一个文件中,浏览历史记录,等等。欲得到 shell 所支持特性的更详细解释,请参阅文档

不仅仅命令行 shell 得到了提高,Swing 控制台也有改进,有了新的工具条,先进的 undo 能力,可以增大或者缩小字体,语法高亮等,总之,控制台有了很多提高。

IntelliJ IDEA JetGroovy 插件

JetGroovy 插件是最棒的工具支持:一个免费而且开源的专用于支持 Groovy 和 Grails 的 IntelliJ IDEA 插件。这个插件是由 JetBrains 他们自己开发的,对于语言和 Web 框架都提供了无以伦比的支持。

插件对 Groovy 有专门的支持,其中部分特性:

  • 对于所有的语法都可以语法高亮,对于未识别的类型加不同的警告。

  • 可以运行 Groovy 类,脚本和用 Groovy 写的 JUnit 测试用例。

  • 调试器:你可以一步一步地运行你的 Java 和 Groovy 代码,设置断点,显示变量,当前的堆栈信息等等。

  • 联合编译器:编译器将 Groovy 和 Java 一起编译,可以解决语言之间的依赖问题。

  • 代码补全,可以补全包,类,属性,成员变量,变量,方法,关键字,甚至对于 Swing UI builder 有特殊的支持。

  • 先进的类搜索和发现功能。

  • 重构:大多数在 Java 中你所喜爱的常用重构功能都可以在 Java 和 Groovy 中使用,例如“surround with”,介绍、内联或者重命名一个变量,重命名包、类、方法和成员变量。

  • 导入优化和代码格式化。

  • 结构视图:对你的类有一个鸟瞰视图。

最终,考虑到在 IntelliJ IDEA 中提供的支持和相互影响的程度,你甚至不会意识到你是在 Groovy 中还是在 Java 中开发一个类。如果你正在考虑在你的 Java 项目中增加一些 Groovy 或者你打算开发 Grails 应用,这个插件是肯定要安装的。

你可以在 JetBrains 站点得到更多信息。

尽管我仅仅表扬了 IntelliJ IDEA 的 Groovy 插件,但是你不必因此改变你的 Groovy 开发习惯。你可以使用由 IBM 的 Zero 项目开发者持续改进的 Eclipse 插件,或者 Sun 的 NetBeans 的 Groovy 和 Grails 插件。

性能提高

Groovy 的新版本除了增加新特性,与以前的版本相比还显著地提高了性能,并且降低了内存消耗。在我们的非正式的基准测试中,我们发现与 Groovy 1.5 beta 版相比我们所有测试套件的运行速度有了 15%到 45%的提高 —— 与 Groovy 1.0 相比肯定有更多的提高。虽然还需要开发更正式的基准测试,但是一些开发人员已经证实了这些测试数字,一家保险公司的开发人员正在使用 Groovy 来写他们的策略风险计算引擎的商业规则,另一个公司在高并发机器上运行了多个测试。总的来说,Groovy 在绝大多数情况下会更快,更轻盈。不过在具体的应用中,效果还要看你如何使用 Groovy。

增强的动态能力

由于 Groovy 和 Grails 项目的共生关系,Grails 核心部分中成熟的动态能力已经被引入到 Groovy 中。

Groovy 是一个动态语言:简单的说,这意味着某些事情,例如方法分派发生在运行时,而不是象 Java 和其他语言那样发生在编译时。在 Groovy 中有一个特殊的运行时系统,叫做 MOP(元对象协议 Meta-Object Protocol),负责方法分派逻辑。幸运的是,这个运行时系统非常开放,人们可以深入系统并且改变系统的通常行为。对于每一个 Java 类和每一个 Groovy 实例,都有一个与之相关联的元类(meta-class)代表该对象的运行时行为。Groovy 为你与 MOP 交互提供了几种不同的方法,可以定制元类,可以继承某些基类,但是谢谢 Grails 项目的贡献,有一种更 groovy 的元类:expando 元类。

代码例子可以帮助我们更容易地理解概念。在下面的例子中,字符串 msg 的实例有一个元类,我们可以通过 metaClass 属性访问该元类。然后我们改变String类的元类,为其增加一个新方法,为toUpperCase()方法提供一个速记记法。之后,我们为元类的 up 属性分配一个闭包,这个属性是在我们把闭包分配给它的时候创建的。这个闭包没有参数(因此它以一个箭头开始),我们在闭包的委托之上调用toUpperCase()方法,这个委托是一个特殊的闭包变量,代表着真实的对象(这里是 String 实例)。

def msg = "Hello!"

println msg.metaClass

String.metaClass.up = { -> delegate.toUpperCase() }



assert "HELLO!" == msg.up()

通过这个元类,你可以查询对象有哪些方法或者属性:

// print all the methods

obj.metaClass.methods.each { println it.name }

// print all the properties

obj.metaClass.properties.each { println it.name }

你甚至可以检查某个特定的方法或者属性是否可用,比使用 instanceof 来检查的粒度要小的多:

def msg = 'Hello!'

if (msg.metaClass.respondsTo(msg, 'toUpperCase')) {

println msg.toUpperCase()

}

if (msg.metaClass.hasProperty(msg, 'bytes')) {



println foo.bytes.encodeBase64()

}

这些机制在 Grails web 框架中得到了广泛的使用,例如创建一个动态查找器:由于你可以在一个 Book 领域类上调用一个findByTitle()动态方法,所以在大多数情况下不需要 DAO 类。通过元类,Grails 自动为领域类加入了这样的方法。此外,如果被调用的方法不存在,在第一次调用的时候方法会被创建并缓存。这可以由下面解释的其他高级技巧来完成。

除了我们已经看到的例子,expando 元类也提供了一些补充的功能。在一个 expando 元类中可以加入四个其他方法:

  • invokeMethod() 让你可以拦截所有的方法调用,

  • methodMissing() 仅仅在没有发现其他方法的时候被调用。

  • get/setProperty() 拦截对所有属性的访问,

  • propertyMissing()在没有发现属性的时候被调用。

与以前的 Groovy 版本相比,通过 expando 元类可以更容易定制你的应用行为,并且节约昂贵的开发时间。很明显,不是每个人都需要使用这些技术,但是在许多场合这些技术是很方便的,例如你想应用某些 AOP(面向方面的编程 Aspect Oriented Techniques)来装饰你的类,或者想通过删除某些不必要的冗余代码来简化你的应用的商业逻辑代码并使其可读性更强。

Steroids 之上的 Swing

Groovy 项目有一个天才的 Swing 开发者团队,他们努力工作使得在 Groovy 中用 Swing 来构建用户界面的能力更强大。在 Groovy 中构建 Swing UI 的基石是 SwingBuilder 类:在你的代码中,你可以在语法级别可视化的看到 Swing 组件是如何彼此嵌套的。Groovy web 站点的一个过分简单的例子显示了如何简单地创建一个小的 GUI 程序:

import groovy.swing.SwingBuilder

import java.awt.BorderLayout

import groovy.swing.SwingBuilder



import java.awt.BorderLayout as BL

def swing = new SwingBuilder()



count = 0

def textlabel

def frame = swing.frame(title:'Frame', size:[300,300]) {

borderLayout()

textlabel = label(text:"Clicked ${count} time(s).",

constraints: BL.NORTH)

button(text:'Click Me',

actionPerformed: {count++; textlabel.text =

"Clicked ${count} time(s)."; println "clicked"},

constraints:BorderLayout.SOUTH)

}

frame.pack()

frame.show()

Swing 构建器的概念已经扩展到提供定制的组件工厂。有一些不是缺省包含在 Groovy 中的附加模块,它们把 JIDE 或者 SwingX 项目中的 Swing 组件集成到 Swing 构建器代码中。

在这个版本中,界面部分有很多改进,值得用一整篇文章来叙述。我仅仅列出其中一部分,例如 bind() 方法。受到 JSR (JSR-295) 的 bean 绑定(beans binding)的启发,你可以很容易地将组件或者 bean 绑定到一起,使得它们在对方发生变化的时候作出反应。在下面的例子中,按钮的间隔尺寸会根据滚动条组件的值的变化而变化。

import groovy.swing.SwingBuilder

import java.awt.Insets

swing = new SwingBuilder()



frame = swing.frame {

vbox {

slider(id: 'slider', value:5)

button('Big Button?!', margin:

bind(source: slider,

sourceProperty:'value',

converter: { [it, it, it, it] as Insets }))
}



}

frame.pack()

frame.size = [frame.width + 200, frame.height + 200]

frame.show()

在构建用户界面的时候将组件绑定在一起是非常常见的任务,所以这个任务通过绑定机制被简化了。还可以使用其他的自动绑定方法,但是需要一篇专门的文章来阐述。

在其他新的值得注意的特性中,新增了一些方便的方法,使得闭包可以调用声名狼籍的 SwingUtilities 类,启动新的线程:edt() 将调用 invokeAndWait() 方法, doLater()将会调用invokeLater()方法,doOutside()方法会在一个新线程中启动一个闭包。不再有丑陋的匿名内部类:只要通过这些便捷方法使用闭包就可以!

最后但是也最重要的是,由于 SwingBuilder 的 build() 方法,分离视图的描述与它相关联的行为逻辑变成再简单不过的事情了。你可以创建一个仅仅包含视图的单独的脚本,而与组件的交互或者绑定都在主类中,在 MVC 模式中可以更清晰的分离视图与逻辑部分。

总结

这篇文章列出了 Groovy 1.5 中引人注目的新特性,但是我们仅仅触及了 Groovy 这个新版本的皮毛。重要的亮点主要围绕着 Java 5 的新特性,例如注解、枚举或者泛型:这使得 Groovy 可以完美地与诸如 Spring、Hibernate 或者 JPA 这样的企业框架优美而无缝的集成。得益于改进的语法以及增强的动态能力,Groovy 让你能够创建内嵌的领域特定语言来定制你的商业逻辑,并在应用的扩展点将其方便地集成进来。由于工具支持的大幅改善,开发者的体验有了显著的提高,开发体验不再是采用 Groovy 的一个障碍。总的来说,Groovy 1.5 前所未有的满足了简化开发者生活的目标,Groovy 应该成为所有 Java 开发者工具箱的一部分。

关于作者

Guillaume Laforge 是 Groovy 的项目经理和 JSR-241 规范的领导者,Java 规范请求(Java Specification Request)在 Java 社区过程(Java Community Process)中标准化了 Groovy 语言。他还是 Technology 的副主席以及 G2One, Inc. 的核心创建者,该公司资助并领导着 Groovy 和 Grails 项目的发展。Guillaume 经常在不同的会议谈论 Groovy 和 Grails,例如 JavaOne,JavaPolis,Sun TechDays,Spring Experience,Grails eXchange。

查看英文原文 What's New in Groovy 1.5

评论

发布