Groovy 笔记

Groovy 是什么

Groovy 是一种面向对象的动态类型语言,与 Java 一样运行在 JVM 上。

简介

Apache Groovy 是一个功能强大、可选类型动态语言,有静态编译能力。旨在通过简单、熟悉、易学的语法来提高 Java 平台的开发人员的开发效率。它与任何 Java 程序无缝集成,并可以为你的应用提供强大的功能,包括脚本能力、领域特定语言(Domain-Specific Language)、运行和编译时元编程函数式编程。

JDK 和 GDK 之间的关系

GDK 是基于 JDK 的,所以在 Java 代码和 Groovy 代码之间传递对象时,无需任何转换。当处在同一 JVM 中时,Java 端和 Groovy 端使用的是同一对象

特点

Groovy 还有其他一些使这门语言更为轻量级、更为易用的特性,试举几例如下:

语法示例

public class GroovyTest { // sum method def static sum(n, closure) { for(int i = 2; i <= n; i += 2) { closure(i) } // return 2 2 } def static curried(closure) { Date date = new Date() def curriedClosure = closure.curry(date) curriedClosure("papapa") } def static paramsDemo(a, b, c, closure) { closure(a + b + c, a + b) } def static dynamic(data, closure){ if(closure.maximumNumberOfParameters == 1){ closure(data) }else{ closure(data, " lala ") } } def static examingClosure(closure) {closure()} public static void main(String []args){ println "java in groovy!" for(int i = 0;i < 10;i++) { System.out.print(i); print i + "," } println "\nrange for"; for (i in 0..2) { print i } println "\nclosure" 0.upto(10){ print "$it" } println "\nrepeat " 3.times { print "index $it," print "ho " } println "\nstep demo" 1.step(10, 5) { print "$it," } println "\nsystem command invocation!" print "java -version".execute().text; println "\na new way to check null" String a = null a?.print() println "\ncatch exception is not necessary!" println "semicolon is not necessary!" // evaluate expression in string println "${1==0}" def total = 1 // 666 println "${sum(10, {index -> println "$index";total *= index})}" println total sum(10){index -> total *= index} println total paramsDemo("hello ", "world", "!") { cb1, cb2 -> println cb1 + cb2 } curried(){ date, event -> println date.toString() + " " + event } dynamic("666"){ data -> println data } dynamic("666") { aa, bb -> println aa + bb } dynamic("666") { // default maxmiumNumberOfParameters == 1 println it } examingClosure() { -> L:{ println getClass().name println this.getClass().superclass.name println owner.getClass().superclass.name println delegate.getClass().superclass.name examingClosure() { println getClass().name println this.getClass().superclass.name println owner.getClass().superclass.name println delegate.getClass().superclass.name } } } // string minus def str = "It's a rainy day in Seattle" println str str -= "rainy " println str // turn string to regex pattern str = ~'hello' println str.getClass().name // list def lst = [1,2,3,5,8,13] println lst.getClass().name println lst[0] println lst[-1] println lst[2..3] def left = 3 def right = 4 println lst[left..right] // list ops lst.each(){println it} println lst.collect{ it * 2} println lst.find{ it == 13} println lst.collect{it}.sum() def flattened = [["ni", "shi"], "sha", "bi", "ba"].flatten() println flattened println flattened - ["ni", "shi"] println flattened*.size() lst.with{ add(3) println "with" println size() println contains(3) } // map def langs = ['C++' : 'Stroustrup', 'Java' : 'Gosling', 'Lisp' : 'McCarthy', 'AD': "99"] println langs.getClass().name // map ops println langs["C++"] println langs."C++" langs.each{item -> println item} langs.each{k, v -> println k + ":" + v} println langs.any{k, v -> v =~ ~"[A-Za-z]"} println langs.every{k, v -> v =~ ~"[A-Za-z]"} def friends = [ briang : 'Brian Goetz', brians : 'Brian Sletten', davidb : 'David Bock', davidg : 'David Geary', scottd : 'Scott Davis', scottl : 'Scott Leberknight', stuarth : 'Stuart Halloway'] def groupByFirstName = friends.groupBy { it.value.split(' ')[0] } groupByFirstName.each { firstName, buddies -> println "$firstName : ${buddies.collect { key, fullName -> fullName }.join(', ')}" } } }

存在的陷阱

// Java 代码 public void method() { System.out.println("in method1"); { System.out.println("in block"); } }

Java 中的代码块定义了一个新的作用域,但是 Groovy 会感到困扰。Groovy 编译器会错误地认为我们是要定义一个闭包,并给出编译错误。在 Groovy 中,方法内不能有任何这样的代码块。

int[] arr = new int[] {1, 2, 3, 4, 5}; // java int[] arr = [1, 2, 3, 4, 5] // groovy def arr = [1, 2, 3, 4, 5] as int[] // groovy

哪些情况下需指明类型

在使用 Groovy 编程时,我倾向于省略类型. 当然在必要的情况下,我也会选择指明类型。比如,

Groovy 和 Java 混用

要在 Groovy 代码中使用 Groovy 类,无需做什么,直接就可以工作。我们只需要确保所依赖的类在类路径(classpath)下,要么是源代码,要么是字节码。要把一个 Groovy 脚本拉到我们的 Groovy 代码中,可以使用 GroovyShell。而如果要在 Java 类中使用 Groovy 脚本,则可以使用 JSR 223 提供的 ScriptEngine API。如果想在 Java 中使用 Groovy 类,或者想在 Groovy 中使用 Java 类,我们可以利用 Groovy 的联合编译(joint-compilation)工具

运行 groovy

要运行 Groovy 代码,我们有两个选择。

java -classpath $GROOVY_HOME/embeddable/groovy-all-2.1.0.jar:. $groovy_file
在 Groovy 中使用 Groovy 类

要在 Groovy 代码中使用一个 Groovy 类,只需要确保该类在我们的 classpath 下。可以使用 Groovy 源代码,也可以把源代码编译成.class 文件并使用该文件——随我们选择。当我们的 Groovy 代码引用了一个 Groovy 类时,Groovy 会以该类的名字在我们的 classpath 下查找.groovy 文件;如果找不到,则以同样的名字查找.class 文件.

利用联合编译混合使用 Groovy 和 Java

如果 Groovy 类是预先编译好的,那我们就可以方便地在 Java 中使用.class 文件或 JAR 包。来自 Java 的字节码和来自 Groovy 的字节码,对 Java 而言没什么区别;我们必须把 Groovy JAR 文件放在我们的 classpath 下,类似于我们使用 Spring、Hibernate 或其他框架/类库的 JAR 文件时的做法。

如果我们只有 Groovy 源代码,而非字节码,又会怎样呢?请记住,当我们的 Java 类依赖其他 Java 类时,如果没有找到字节码,javac 将编译它认为必要的任何 Java 类。不过 javac 对 Groovy 可没这么友好。幸好 groovyc 支持联合编译。当我们编译 Groovy 代码时,它会确定是否有任何需要编译的 Java 类,并负责编译它们。因此我们可以自由地在项目中混合使用 Java 代码和 Groovy 代码,而且不必执行单独的编译步骤。简单地调用 groovyc 就好。

要利用联合编译,我们需要使用-j 编译标志。使用-J 前缀把标志传给 groovy 编译器.

groovyc -j JavaClass.java UseJavaClass.groovy -Jsource 1.6

-Jsource 1.6 会把可选的选项 source = 1.6 发送给编译器。使用 javap 检查生成的字节码,会发现 JavaClass 作为一个普通的 Java 类,扩展了 java.lang. Object,而 UseJavaClass 扩展了 groovy.lang. Script。

在 Java 中创建与传递 Groovy 闭包

如果仔细检查,我们会发现,当 Groovy 调用一个闭包时,它只是使用了一个名为 call()的特殊方法。要在 Java 中创建一个闭包,我们只需要一个包含该方法的类。如果 Groovy 代码要向闭包传递实参,我们必须确保 call()方法接受这些实参作为形参.

在 Java 中调用 Groovy 动态方法

每个 Groovy 对象都实现了 GroovyObject 接口,该接口有一个叫作 invokeMethod()的特殊方法。该方法接受要调用的方法的名字,以及要传递的参数。在 Java 这一端,invokeMethod()可以用来调用 Groovy 中使用元编程动态定义的方法. 从 Java 中可以使用任何 Groovy 类,这点没有限制,不管它们是否是动态的。

在 Groovy 中使用 Java 类

在 Groovy 中使用 Java 类简单且直接。如果我们想使用的 Java 类是 JDK 的一部分,可以像在 Java 中那样导入这些类或它们的包。Groovy 默认会导入很多包和类,因此,如果我们想使用的类已经导入(比如 java.util. Date),直接用就可以了,无需再导入.

如果想使用一个自己的 Java 类,或者不是标准 JDK 中的类,在 Groovy 中可以像在 Java 中那样导入它们。请确保导入了必要的包或类,或者使用类的全限定名来引用它们。当运行 groovy 时,可以使用-classpath 选项指定.class 文件或 JAR 包的路径。如果类文件和我们的 Groovy 代码在同一目录下,则不需要通过该选项指定目录。

如果想显式地编译 Groovy 调用了 java 的代码,不必分别编译 Java 和 Groovy。可以使用联合编译代替。

从 Groovy 中使用 Groovy 脚本

使用 GroovyShell,我们可以在任何文件(或字符串)中对脚本调用 evaluate()方法,以执行该脚本。

evaluate(new File('Script1.groovy'))

如果我们想向脚本传递一些参数,又该怎么办呢?我们可以使用一个 Binding 实例来绑定变量

println "In Script2" name = "Venkat" shell = new GroovyShell(binding) result = shell.evaluate(new File('Script1a.groovy')) println "Script1a returned : $result" println "Hello $name"

在发起调用的脚本中,我们创建了一个变量 name(与被调用脚本中用到的变量名字相同)。当创建 GroovyShell 的实例时,我们将当前的 Binding 对象传给了它(每个脚本执行时都有一个这样的对象)。被调用脚本现在就可以使用(读取和设置)发起调用脚本所知道的变量了.

如果不希望影响当前的 binding, 而是想将其与被调用脚本的 binding 分开,我们只需要创建一个新的 Binding 实例,在该实例上调用 setProperty()来设置变量名和值,并将其作为创建 GroovyShell 实例时的一个参数

println "In Script3" binding1 = new Binding() binding1.setProperty('name', 'Venkat') shell = new GroovyShell(binding1) shell.evaluate(new File('Script1a.groovy')) binding2 = new Binding() binding2.setProperty('name', 'Dan') shell.binding = binding2 shell.evaluate(new File('Script1a.groovy'))
从 Java 中使用 Groovy 脚本

如果想在 Java 中使用 Groovy 脚本,我们可以利用 JSR 223(JSR 即 Java Specification Request,Java 规范请求)需要确保…/jsr223-engines/groovy/build/groovy-engine.jar 在我们的 classpath 下

MOP 与元编程

元编程(meta programming)意味着编写能够操作程序的程序,包括操作程序自身。像 Groovy 这样的动态语言通过元对象协议(MetaObject Protocol,MOP)提供了这种能力。利用 Groovy 的 MOP,创建类、编写单元测试和引入模拟对象都很容易。在 Groovy 中,使用 MOP 可以动态调用方法,甚至在运行时合成类和方法。借助 MOP,在 Groovy 中可以创建内部的领域特定语言。

Groovy 对象

Groovy 对象是带有附加功能的 Java 对象。在一个 Groovy 应用中,我们会使用三类对象:POJO、POGO 和 Groovy 拦截器。POJO(Plain Old Java Object)就是普通的 Java 对象,可以使用 Java 或 JVM 上的其他语言来创建。POGO(Plain Old Groovy Object)是用 Groovy 编写的对象,扩展了 java.lang. Object,同时也实现了 groovy.lang. GroovyObject 接口。Groovy 拦截器是扩展了 GroovyInterceptable 的 Groovy 对象,具有方法拦截功能。Groovy 这样定义的 GroovyObject 接口:

//这是 Groovy 源代码中 GroovyObject.java 的一个片段 package groovy.lang; public interface GroovyObject { Object invokeMethod(String name, Object args); Object getProperty(String property); void setProperty(String property, Object newValue); MetaClass getMetaClass(); void setMetaClass(MetaClass metaClass); }

我们可以通过调用 setMetaClass()修改它的 MetaClass。这给我们一种对象在运行时修改了它的类的感觉.

当我们调用一个方法时,Groovy 会检查目标对象是一个 POJO 还是一个 POGO. 对于一个 POJO,Groovy 会去应用类(application-wide)的 MetaClassRegistry 取它的 MetaClass,并将方法调用委托给它。因此,我们在它的 MetaClass 上定义的任何拦截器或方法,都优先于 POJO 原来的方法.

对于一个 POGO, 如果对象实现了 GroovyInterceptable,那么所有的调用都会被路由给它的 invokeMethod() , 在这个拦截器内,我们可以把调用路由给实际的方法,使类 AOP(Aspect-Oriented Programming,面向方面编程)的操作成为可能. 如果该 POGO 没有实现 GroovyInterceptable,那么 Groovy 会先查找它的 MetaClass 中的方法,之后,如果没有找到,则查找 POGO 自身上的方法。 如果该 POGO 没有这样的方法,Groovy 会以方法名查找属性或字段。如果属性或字段是 Closure 类型的,Groovy 会调用它,替代方法调用。如果 Groovy 没有找到这样的属性或字段,它会做最后两次尝试。如果 POGO 有一个名为 methodMissing()的方法,则调用该方法。否则调用 POGO 的 invokeMethod()。如果我们在 POGO 上实现了这个方法,它会被调用。invokeMethod()的默认实现会抛出一个 MissingMethodException .

使用 MOP 拦截方法

MOP 方法注入

使用 MiXin 注入方法

class Friend { def listen() { "$name is listening as a friend" } }

name 属性本身没有在这个类中做任何定义,它将由该类所混入的类提供。

首先,可以使用@Mixin 注解语法,将 Mixin 注入到 Person 类中(如下面代码所示)。作为一种选择,也可以使用静态初始化器将 Mixin 引入到这个类中,就像这样:class Person { static { mixin Friend} ...}。

@Mixin(Friend) class Person { String firstName String lastName String getName() { "$firstName $lastName"} } john = new Person(firstName: "John", lastName: "Smith") println john.listen()

在这个例子中,Friend 的方法被添加到了 Person 中. 通过向注解提供由多个类名组成的列表,可以混入多个类,就像这样:@Mixin([Friend, Teacher])。