BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

Groovy 1.5的新特性

| 作者 Guillaume LaForge 关注 1 他的粉丝 ,译者 曹云飞 关注 0 他的粉丝 发布于 2008年1月17日. 估计阅读时间: 49 分钟 | QCon上海2018 关注大数据平台技术选型、搭建、系统迁移和优化的经验。

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"
break
default:
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

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

希望性能能够再提升,呵呵 by Guo Ford

介绍的很详细,赞一个!

好文章 by wei edon

groovy好,翻译者翻译得更好,加油!

Re: 好文章 by 曹 云飞

谢谢,我们共同进步:)

嗯,这篇文章好,介绍得很详细。 by Liu Sam

越来越喜欢Groovy和Grails,有点追不上它们的发展了,好!

翻译得也很好,赞!

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

4 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT