Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

Groovy学习笔记

14
Feb
2014

Groovy学习笔记

By Alex
/ in Java
/ tags Groovy
0 Comments
基础知识
Groovy简介

Groovy诞生于2004年, 它是可选类型的(optionally typed)动态语言,是最流行的JVM语言之一,比起其它语言,Groovy能够与Java无缝集成、相互调用,这是它的最大优势。

Groovy可以作为脚本语言使用(但是执行时,即便是脚本也会编译为Java类),也可以编译为Java类。在运行时,你只需要把 groovy-all-*.jar 放在Classpath中,无需其它任何依赖或者执行引擎。

使用Groovy,你可以:

  1. 让Java代码更加简洁
  2. 自动化重复的任务
  3. 使用DSLs(domain-specific languages)进行业务建模
  4. 执行即席的脚本任务

Groovy有以下高级特性:

  1. 支持闭包(Closures)
  2. 支持动态方法(dynamic methods)
  3.  为Java平台引入元对象协议(MOP,Meta Object Protocol)
安装Groovy

可以到官网下载Groovy运行时,并将安装目录设置为环境变量 GROOVY_HOME ,同时把 $GROOVY_HOME/bin 添加到环境变量PATH中。

Groovy命令行工具
工具 说明 
groovysh 启动交互式Shell
groovyConsole 运行一个GUI来交互执行Groovy代码 
groovy 开始解释Groovy脚本。作为参数的脚本只需要在Classpath中存在即可
Groovy在JVM中的运行方式
  1. 预编译模式(Precompiled mode):使用Groovy编译器——groovyc,把*.groovy编译为*.class文件。并把*.class放置到Classpath下,通过Java的类加载器加载这些类并运行
  2. 直接模式(Direct mode):通过groovy的类加载器,在运行时直接加载*.groovy文件,并动态的生成JVM对象,这种情况下,没有生成.class文件,但是在JVM中是有对应的java.lang.Class实例的

Groovy在源代码级别增强了Java,但是两者的字节码格式是完全相同的。

GDK

GDK即Groovy Development Kit,是Groovy对JDK的补充,引入了一些新的类,并为已经现有的一些Java类增强了功能。下面列出GDK的一些特性:

  1. 为所有类型引入 size() 方法
  2. 简化的数据库访问
  3. 简化的XML处理
动态性

动态性是指在运行时修改程序结构的能力。

Groovy生成的字节码,会在运行时转换为普通的Java类。除非使用特殊的机制影响JVM,那么这些已加载的Java类的结构是无法动态改变的。

Groovy为什么能具有动态语言的特征,是因为其生成的字节码,不是直接调用方法,而是像这样:

Groovy
1
getMetaClass().invokeMethod(this, "method", EMPTY_PARAMS_ARRAY)

也就是说,方法调用以反射的方式转给对象的MetaClass进行处理, 这就类似于Cglib等字节码增强工具一样,可以在运行时拦截、重定向、增加/删除方法。

Groovy语言
与Java在语法上的异同

Groovy和Java在语法上的共同点包括:

  1. 相同的包处理机制(包括包的声明和import语句)
  2. 类和方法的定义(嵌套类除外)
  3. 控制结构语句(经典的C风格for循环除外)
  4. 操作符、表达式和赋值
  5. 异常处理
  6. 变量声明
  7. 对象实例化,引用和取消引用对象
  8. 方法调用

Groovy和Java在语法上的差异点包括:

  1. 非静态内部类的实例化语法:
    Groovy
    1
    new OuterClass.NonStaticInnerClass( outClassInstance,args )

Groovy还引入了新的语法特性:

  1. 通过新的表达式和操作符访问Java对象
  2. 更多的对象声明方式
  3. 引入新的控制结构
  4. 引入新的数据类型、操作符和表达式
  5. 顶级表达式中的方法调用的括号可以省略,前提是被调用方法有至少一个参数(这是为了防止与属性访问歧义,该限制可以通过元编程突破)。链式方法调用与该规则配合使用,可以形成自然语言风格,在DSL中常用
  6. 作为最后一个入参的闭包,可以放在方法调用括号的外面,这一规则可以和上一条联用
  7. 方法重载发生在运行期而非编译期
自动导入的包和类
Groovy
1
2
3
4
5
6
7
8
groovy.lang.*
groovy.util.*
java.lang.*
java.util.*
java.net.*
java.io.*
java.math.BigInteger
java.math.BigDecimal

上述包和类自动导入,而Java仅仅导入java.lang包。

基础代码示例
Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package cc.gmem.study.groovy
//声明类
class Person
{
    //声明成员变量
    private int id
    //声明一个属性
    String name
    /**
     * GroovyDoc注释
     * @param name 名称
     */
    Person(String name)
    {
        this.name = name
    }
    /* 程序入口点:main方法 */
    static main(args)
    {
        //方法调用的括号,在不引起歧义的前提下可以省略
        System.out.println  'Groovy'  //字符串可以用单引号界定
 
        // 多个参数
        httpRequest "https://blog.gmem.cc", "application/json"
        // 命令参数
        httpRequest url: "https://blog.gmem.cc",
                    acceptType:"application/json"
 
        1.equals( 1 )  //万物皆对象
 
        //断言
        assert 1 == 1  // ==调用equals()方法进行判断,而不是比较对象地址
        xx = 1  // 如果局部变量xx不存在,等价于this.xx = 1
        //def用于声明“动态类型”
        def x = 1
        //打印变量的值
        println x
 
        def person = new Person('Alex')
        person.setName( 'Alex Wong' ) //getter/setter直接可以用,不需要编写方法
        person.name = 'Alex Wong' //与上面效果一样,会访问setter而不是直接访问属性
 
        def sls = '单引号字符串等价于java.lang.String'
        char a = 'x'  //字符类型必须静态声明,或者  'x' as char
        //groovy.lang.GString:双引号字符串中的占位符可以动态解释,在必要时,Groovy可以在GString和String之间进行转换
        println "Hello $person.name" //占位符以$开始,支持属性导航,可以使用${}形式,{}中可以是任何表达式
        def mls = '''多行
字符串'''
 
        //语言级别的正则式支持
        // =~表示查找操作符
        assert person.name =~ /\sWong/  //斜线界定的字符串,表示不转义反斜杠(\),在正则式中特别有用
        assert 'Alex Wang' == person.name.replaceAll( /Wong/, 'Wang' ) //替换
 
        //java.math.BigInteger、java.math.BigDecimal直接量
        def bigInt = 1000g
        def bigDec = 1000.01g
 
        //列表直接量
        def roman = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII']
        assert roman[4] == 'IV' //数字索引语法访问元素
        roman[8] = 'VIII' //自动扩展列表
 
        //映射(字典直接量)
        def http = [
            100 : 'CONTINUE',
            200 : 'OK',
        ]
        assert http[200] == 'OK'
        http[400] = 'BAD REQUEST' //动态插入键值对
 
        //语言级别的范围(range)支持
        def range = 1..10
        assert range.contains(5)
        assert range.contains(15) == false
        assert range.size() == 10
        assert range.from == 1
        assert range.to == 10
        assert range.reverse() == 10..1
 
        //闭包支持,闭包是花括号限定的代码块,其入参列表以 ->结束
        def c = {el ->  println el }
        [1, 2, 3].each (c)
    }
}
操作符重载
操作符  对应方法  适用于
a + b a.plus(b) Number, String,StringBuffer, Collection,Map, Date, Duration
a – b  a.minus(b) 

Number, String, List, Set, Date, Duration 

Groovy
1
"Alex Wong" - 'Alex ' == 'Wong'
a * b  a.multiply(b)  Number, String, Collection 
a / b a.div(b)  Number 
a % b  a.mod(b)  Integral number 
a++ (++a) a.next()  Iterator, Number, String, Date, Range 
a-- (--a)  a.previous()  Iterator, Number, String, Date, Range 
-a  a.unaryMinus()  Number, ArrayList 
+a  a.unaryPlus()  Number, ArrayList 
a ** b  a.power(b)  Number 
a | b  a.or(b)  Number, Boolean, BitSet, Process 
a & b  a.and(b)  Number, Boolean, BitSet 
a ^ b  a.xor(b)  Number, Boolean, BitSet 
~a  a.bitwiseNegate()  Number, String(返回正则式) 
a[b]  a.getAt(b)  Object, List, Map, CharSequence, Matcher 
a[b] = c a.putAt(b, c) Object, List, Map, StringBuffer
a << b a.leftShift(b) integral number, StringBuffer, Writer, File, Socket, List
a >> b a.rightShift(b) Number
a >>> b a.rightShiftUnsigned(b) Number
a == b a.equals(b) Object
a != b !(a == b) Object
a <=> b a.compareTo(b) java.lang.Comparable
a > b a.compareTo(b) > 0
a >= b a.compareTo(b) >= 0
a < b a.compareTo(b) < 0
a <= b a.compareTo(b) <= 0
a as type a.asType (typeClass) Any type

自己实现操作符重载时,可以重载上述方法,以便针对具体类型实现二元操作符,例如: 

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Currency
{
    int rate
    int amount
    Currency(int rate,int amount)
    {
        this.rate = rate
        this.amount = amount
    }
 
    Currency plus(Currency c)
    {
        //注意这里体现了Groovy的类型转换原则:返回一个更一般的类型
        def add = (amount + c.amount*c.rate / rate) as int
        return new Currency(rate, add)
    }
    Currency plus(Integer c)
    {
        return new Currency(rate, amount + c)
    }
    @Override
    public String toString()
    {
        return amount
    }
}
 
def usd = new Currency(6, 60 )
def cny = new Currency(1, 60 )
println usd + cny
println usd + 60
内置数据类型

Groovy在语言层面支持一套数据类型,包括字符串、正则式、数字、范围、列表、映射等。此外,不像Java,Groovy不存在所谓基本类型。 

万物皆对象

在groovy中,万事万物皆对象,这与Java不同,后者包含若干基本类型(int/double/char/byte),基本类型是专有类型,其它类型是引用类型。 这些专有类型:

  1. 其变量存放了数据本身,而不是数据的地址
  2. 不能应用面向对象的编程风格——方法调用、属性导航。不能从java.lang.Object继承方法
  3. 能应用多种运算符,而对应的引用类型则基本不能应用这些运算符
数字类型与自动拆装箱

Groovy扩展了Java数字类型的直接量表示:

Java类型 直接量语法
java.math.BigInteger 612g,1106G  
java.math.BigDecimal 3.14, 6.02E23, 94605.56G

Groovy调用Java方法时,数字类型的拆装箱完全自动进行。

可选(动态)类型

Groovy的变量声明可以没有类型信息,以 def 开头声明的对象,称为动态类型,动态类型指向的对象的类型可以动态变化: 

Groovy
1
2
3
4
5
6
7
def dynaVar = 10G
//改变对象类型
dynaVar = "$dynaVar"
 
//如果声明为静态类型,则不能改变
BigDecimal bd = 10G
bd = "" // 报错:Cannot cast object '' with class 'java.lang.String' to class 'java.math.BigDecimal'
字符串

Groovy引入了 groovy.lang.GString  类型,用来支持占位符替换。Groovy支持多种字符串表示形式:

  1. 单引号包围的字符串:不支持占位符替换,相当于Java的字符串。如果要表示 char 类型,则必须静态声明或者使用 as 操作符Cast
  2. 双引号包围的字符缓存:支持占位符变量替换,占位符形式为: $varName 或 ${obj.propName} 
  3. 三引号包围的字符串:支持多行。如果是三个双引号,则支持占位符替换
  4. 斜杠包围的字符串:不需要对反斜杠进行转义,主要用于正则式

Groovy为字符串引入了一些新方法,包括: toInteger, toLong, toFloat, toDouble 等。

可以对字符串进行追加、修改等操作:

Groovy
1
2
3
4
5
6
7
8
greeting = 'Hello'
greeting <<= ' Groovy' //可以对字符串进行内容追加
//追加后自动转换为StringBuffer
assert greeting instanceof java.lang.StringBuffer
greeting << '!'
assert greeting.toString() == 'Hello Groovy!' //不会自动转回String,因此需要调用toString()
greeting[1..4] = 'i' //可以用这种语法替换范围指定的子串
assert greeting.toString() == 'Hi Groovy!'
正则表达式

Groovy引入了三个操作符,专用于正则式处理 

操作符 说明 
=~ 正则式匹配操作符
==~  正则式匹配整串操作符 
~String 正则式模式(Pattern)操作符,把字符串转换为java.util.regex.Pattern类型

下面是一些示例代码:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.regex.Matcher;
 
def pattern = /W.ng/
def name = 'Alex Wong Wang'
def lname = 'Wang'
 
//完整匹配,返回值是一个Boolean对象
matcher =  lname ==~ pattern
assert matcher instanceof Boolean
 
//子串匹配,返回值是一个matcher对象
matcher = ( name =~ pattern )
assert matcher instanceof Matcher
//支持使用索引语法访问匹配的子串
assert matcher[0] == 'Wong'
assert matcher.count == 2
 
//预编译正则式
pattern = ~/W.ng/
pattern.matcher( 'Alex Wong' )[0]
数字处理和自动转型

Groovy中的数字处理更加直观和符合预期,例如 1/2 的结果会是0.5而非0。

在Groovy中, + - * 运算满足下列规则:

  1. 如果有一个数为Float或者Double,那么结果是Double
  2. 否则,如果一个操作数为BigDecimal,结果为BigDecimal
  3. 否则,如果一个操作数为BigInteger,结果为BigInteger
  4. 否则,如果一个操作数为Long,那么结果为Long
  5. 否则,结果为一个Integer

对于 / 运算:

  1. 如果任何一个数是Float(或者Double)类型,那么运算结果为Double类型
  2. 否则,结果为BigDecimal类型,精度以两个数的精度值较大的为准,采用四舍五入的方式,结尾没有无效的0
  3. 整数除法可以使用 as 转换,或者使用 intdiv() 方法

当运算结果超过类型表示范围后,不会自动提升类型,幂运算除外。幂运算根据需要依 Integer,Long,Double 的次序提升。

范围(Range)

 Groovy引入一个groovy.lang.Range类,表示一个连续的对象范围,并在语法级别上支持范围的直接量:

Groovy
1
2
3
4
5
6
7
8
left = 0
right = 10
Range r = left..right
r = (left..right)
//小于号表示右排除范围,即范围不包含right这个值
r = (left..<right)
//起始值大于结束值的范围称为“反向范围”
r = 10..0

范围的元素不一定是数字。 日期、字符也可以:

Groovy
1
2
3
4
5
assert ('a'..'d').size() == 4
 
def today = new Date()
def tomorrow = today + 1
assert (today..tomorrow).size() == 2

Range的 toList() 方法用于生成对应的列表。

可以使用下面的方式遍历Range:

Groovy
1
2
3
4
//遍历Range
for ( day in today..tomorrow) println day
//使用闭包遍历
(today..tomorrow).each { day -> println day }
列表(List)

Groovy把Java数字和List的优点融合到了一起:基于索引的操作、动态增减元素。Groovy在语法上修改了List直接量的声明方式:

Groovy
1
list = [1, 2, 3]

 Groovy缺省使用的List实现是 java.util.ArrayList ,你可以显示声明为其它类型。 Groovy为List重载了 [] 操作符,支持多种下标访问风格:

Groovy
1
2
3
4
5
6
7
8
9
10
11
list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
//基于Range的范围访问
assert list[2..4]  == [2, 3, 4] //getAt
//基于索引集合的访问
assert list[2, 3, 4]  == [2, 3, 4]
 
list[2..4]  = [2, 3, 4] //setAt 设置元素
list[5..9] = []  //移除元素
println list // [0, 1, 2, 3, 4]
list[1..<1] = [9, 9, 9] //在索引1上插入多个元素
println list // [0, 9, 9, 9, 2, 3, 4]

List支持多种操作符:

Groovy
1
2
3
4
5
6
list = []
list += 1 // [1]
list += [2, 3] // [1, 2, 3]
list << 4 << 5 // [1, 2, 3, 4, 5]
list -= [1, 2, 3, 4] // [5]
list * 3 // [5, 5, 5]
映射(Map)

与List类似,Groovy也从语法上支持Map。和其它语言使用的花括号不同,Groovy用方括号界定Map:

Groovy
1
2
3
4
map = [
    'Alex' : 'Wong',
    'Meng' : 'Lee'
]

如果要声明一个空的Map,使用 [:]  。Groovy默认使用java.util.HashMap。由于Map的Key往往是字符串,因此,Key的字符串标记可以省略: assert ['a':1] == [a:1] ,但是Key不能是Groovy关键字或者包含特殊符号。

除了数组语法外,还可以使用点号导航来访问Map元素:

Groovy
1
2
myMap = ['a.b':1]
assert myMap.'a.b' == 1
闭包

所谓闭包(Closure),就是绑定了上下文中变量的匿名函数。闭包在很多语言中都可以见到,但是在Java领域,直到Java 8才通过Lambda语法提供闭包特性。Groovy引入 groovy.lang.Closure 表示闭包,并在语法层面上支持闭包的声明。闭包是普通对象,可以被传递。声明一个闭包很简单:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
list = [1, 2, 3]
counter = 0
// 闭包语法: {入参列表 -> 语句列表}
Closure  c = {val -> counter++; ++val} //闭包的最后一个表达式,作为默认的返回值,任何时候可以显式的return
list.each c
 
// 匿名的闭包
list.each {val -> ++val}
// 小箭头也可以省略
def c = { println it }
 
// 作为参数的闭包,可以使用注解说明其入参类型,这些注解主要供IDE的智能提示
static int shell(@ClosureParams( value = SimpleType.class, options = "java.io.Reader" ) Closure stdoutReader){}
// 多参数闭包入参类型提示
class Lists {
  static <T,U> List<T> randomInstances(List<U> listToIterate, final Builder<T> builder,
        @ClosureParams(value = FromString, options = ["T,U", "T"]) final Closure<T> postProcessor = null) {
    }
  }
}
 
// 闭包的返回值,可以在其泛型参数中声明
public abstract class Closure<V> extends GroovyObjectSupport{}

注意,闭包具有访问上下文中变量的能力(上面第4行),这意味着上下文变量以引用的形式(而不是值)传递给闭包。特别是,即使上下文变量的Scope已经终结,被闭包引用的那些变量依然存在:

Groovy
1
2
3
4
5
6
7
def genCloseure(){
    def i = 10
    return {println i++}
}
def c = genCloseure() //i作为局部变量,正常情况下应该在调用完毕即销毁
c() // 10,i还存活着
c() // 11

在闭包中,关键字:

  1. this 表示声明闭包的对象或者类
  2. owner 表示直接包含闭包的对象或类
  3. delegate 默认值为owner,该变量用于重新定位闭包体中引用的上下文变量,将它们解析为自己的属性/方法

下面的例子说明这三者的关系:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package cc.gmem.study.groovy
def cc = { // 外层闭包
    return {  // 内层闭包
        if (printhis){
            println this      //当前对象cc.gmem.study.groovy.GroovyStudy@4485af5c
            println owner     //外层闭包cc.gmem.study.groovy.GroovyStudy$_run_closure1@15b734b
            println delegate  //默认就是owner,可以运行时替换
        }else{
            println name      //自动解析的上下文变量
        }
    }
}
def c = cc()
 
printhis = true
c()
printhis = false
 
// 修改delegate对象
c.delegate = [name : 'DelegateObject']
c() // 打印DelegateObject
c.name = 'InnerClosure' //设置内层闭包的属性
/**
* 下面两行打印:
* name of inner closure is InnerClosure
* name of enclosing closure is InnerClosure
* name of current object is InnerClosure
* 可以看到,设置闭包的xxx属性,那么owner.xxx也被设置,且这一过程会递归进行
* 在闭包里,this.xxx和owner.xxx是同义词,但是this和owner则可能不是一个对象
*/
println "name of inner closure is $c.name"
println "name of enclosing closure is $cc.name"
println "name of current object is $name"
 
c() // 打印InnerClosure

设置闭包的 resolveStrategy 属性可以改变默认的上下文变量搜索规则:

  1. OWNER_FIRST :默认,优先搜索owner
  2. OWNER_ONLY :仅搜索owner
  3. DELEGATE_FIRST :优先搜索delegate
  4. DELEGATE_ONLY :仅搜索delegate
  5. TO_SELF :owner、delegate都不搜索

当然,你随时可以使用 this 、 owner 、 delegate 显式的访问特定上下文对象的属性,不受上述搜索规则限制。此外,局部变量的优先级比上面的上下文对象高:

  1. 当前闭包自己定义的局部变量优先级最高
  2. 定义了当前闭包的函数/闭包——即owner——其局部变量的优先级次之。这是闭包的重要特点:在声明时绑定(back reference)owner定义的局部变量
  3. 多层函数/闭包嵌套时,越外层的局部变量,优先级越低
  4. 找不到局部变量,则在this、owner、delegate等上下文对象中寻找属性

如果闭包只有一个参数,那么不需要显式声明,Groovy允许使用 it  来访问唯一入参。

可以把对象方法(不支持静态方法)作为闭包引用: def closure = reference.&methodName 。Groovy支持在运行时进行重载闭包引用:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//方法闭包限制在实例方法上
class Lad{
    def sayHello(){
        sayHello('Hello')
    }
    def sayHello(def hello){
        println hello
    }
}
def c = new Lad().&sayHello //c引用哪个方法在此并不确定
 
// 运行时重载示意:
// 闭包支持直接“调用”,这又是操作符重载的威力。相比之下Java 8 Lambda则不能直接“调用”
c()     // 零参版本
c('Hi') // 一参版本

Groovy为闭包提供了以下重要方法:

方法 说明 
call() 调用闭包,与()操作符一样 
curry()

返回当前闭包绑定了若干参数(从左向右)的副本闭包:

Groovy
1
2
3
def add = {x, y -> return x + y}
def addOne = add.curry( 1 ) //绑定第一个参数
println addOne(100) // 101
isCase()

闭包实现了该方法,可以作为switch语句的分类器使用:

Groovy
1
2
3
4
5
6
def i = 10
switch(i)
{
    //闭包作为分类器
    case { it%2 == 0 }: println true; break
}
注解

在Groovy中可以和Java一样,声明和使用注解。下表列出重要的内置注解:

注解 说明 
@Immutable

标记一个类型为不变类型:为class和所有字段添加final标记,对于那些本身可变的对象,Groovy会强制defensive copying。例如:

Groovy
1
2
3
4
5
6
7
8
9
import groovy.transform.Immutable
@Immutable class ClassRoom
{
    Map<Integer,String> students = new HashMap();
}
ClassRoom cr = new ClassRoom();
 
//Caught: java.lang.UnsupportedOperationException
cr.students.put( 020, "Alex Wong" );
@Grab

在脚本中显式的声明依赖,使其自包含。

Groovy
1
2
@Grab('commons-lang:commons-lang:2.4')
import org.apache.commons.lang.ClassUtils

 IDE或者Maven、Ivy等工具负责识别和定位依赖

@Category

用于声明一个Category,注解参数说明this指向的类型:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
@Category(Integer)
class IntegerMarshal
{
    String marshal()
    {
        toString()
    }
}
use (IntegerMarshal)
{
    assert 1.marshal() == "1"
}
@Mixin

轻松的把其他类定义的方法添加到某个类中:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Mixin(MessageFeature)
class Test
{
    void testWithMixinUsage()
    {
        send "Called from Test"
    }
}
class MessageFeature
{
    void send(String msg)
    {
    }
}

该特性自2.3开始已经废弃,由 trait 代替

@Delegate

自动添加被代理对象的全部行为到当前类:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class CountingList
{
    int counter = 0
    //获得list的全部行为,即,默认的把所有调用转发给list
    @Delegate LinkedList list = new LinkedList<>()
    //覆盖下面的行为
    boolean addAll(Collection<? extends E> c)
    {
        counter += c.size()
        list.addAll(c)
    }
    boolean addAll(int index, Collection<? extends E> c)
    {
        counter += c.size()
        list.addAll(index, c)
    }
}

在实现装饰、代理等模式时,可以避免写大量重复性代码

程序控制结构
布尔值转换规则
运行时数据类型 何时作为true处理
Boolean  如果对应的布尔值为true 
Matcher 如果存在至少一个匹配 
Collection 如果集合非空
Map 如果映射非空
String, GString 如果字符串非空
Number, Character 如果值为非0
其它所有类型 如果为非null
分支结构

需要注意的是,在if语句中,赋值不能作为顶级表达式。这样会导致编译错误: def var ; if( var = 1 ){} 。除了上述布尔值转换规则以外,Groovy的if/else语句完全一样。三元操作符也被支持,和Java的三元操作符类似。

但是,Groovy优雅的增强了Java中的switch-case语句。其引入了灵活分类器机制——任何实现了 isCase() 方法的对象,都可以作为 case 子句的分类器,而Java中仅仅支持常量作为分类器:

Groovy
1
2
3
4
5
6
7
8
9
10
switch (candidate)
{
    case classifier1 : handle1() ; break
    case classifier2 : handle2() ; break
    default : handleDefault()
}
//上面的语句等价于:
if (classifier1.isCase(candidate)) handle1()
else if (classifier2.isCase(candidate)) handle2()
else handleDefault()

只要isCase()返回true,那么当前case就匹配。Groovy中下面的类实现了isCase(): 

类型 实现方式 
Object a.equals(b) 
Class a.isInstance(b)
Collection a.contains(b)
Range a.contains(b)
Pattern a.matcher(b.toString()).matches()
String (a==null && b==null) || a.equals(b)
Closure a.call(b)
循环结构

Groovy中没有Java的 do{}while(condition) 或者 for(;;) 循环语法。支持 while(condition) 语法,并且引入 for (variable in iterable) { body } 语法(与Java5引入的新循环语法类似)。此外,基于闭包的循环也很常用:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
def list = [0, 1, 2, 3, 4]
 
//while循环,注意任何对象都可以作为测试条件
while(list) list.remove( 0 ) //移除直到为空
//for循环
for(item in list) println item
 
//下面的for循环与Java的for(int i = 0;i < 10; i++)类似
for(i in 0..<10) println i
 
//基于闭包的循环
(0..<10).each { println it }
OO编程
类、脚本和文件

在Groovy中,文件和类声明的关系不像Java那样的固定。一个Groovy文件可以包含多个public类声明:

  1. 如果.groovy文件中不包含类声明,它被作为脚本处理。Groovy会生成一个名称与文件名一致的Script类,脚本内容被包含在 run() 方法中,并且新建一个main方法调用之
  2. 如果.groovy文件中只包含一个类声明,并且起名称与文件名相同,那么和Java一样,具有一一对应关系
  3. 如果.groovy文件中包含多个不同访问限定符的类,Groovy会分别生成.class
  4. 如果.groovy文件中混合了脚本和类声明,脚本代码将变成和文件同名的类被执行。此时声明的类不应当和文件同名
增强的导入支持
Groovy
1
2
3
import javax.lang.*
//使用import as可以定义别名,别名可以避免名字冲突,或者避免不合理的名字
import java.lang.AssertionError as AErr 
定义新类型

Groovy的类声明语法和Java很类似,但是需要注意:

  1. 属性、本地变量在使用前必须声明(注意在Groovy脚本中这不是必须的)
  2. 支持Java的访问限定符 private protected public ,如果不加限定符:
    1. 对于成员变量,默认限定符为 private,Groovy会自动生成Getter/Setter方法
    2. 对于成员方法,默认作为 public 的
  3. 成员变量或者方法返回值可以声明为动态类型,使用关键字def
  4. 静态变量或者方法返回值可以声明为动态类型,使用关键字static
  5. 方法参数的类型是可选的,如果不指定,默认Object
  6. 方法参数可以提供默认值
  7. 方法参数可以声明为单个Map,调用时使用命名参数方式传递

下面是一个类定义的例子:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cc.gmem.study.groovy
class Person
{
    def name
    def age
    //构造器
    Person(name = 'Alex', age = 29) //参数默认值
    {
        this.name = name
        this.age = age
    }
    def listArgs(List l){ }
    def namedArgs(Map m){ }
}
使用类和对象

这里我们使用前面定义的新类:

Groovy
1
2
3
4
def p = new Person()
p.namedArgs( key1 : 0, key2 : 1 ) //命名参数方式传参
p.listArgs( [1, 2, 3])
p.listArgs() //任何参数都是可选的

 在Groovy中,对象属性/方法的访问,可以使用点号导航或者下标语法,甚至,点号导航中可以使用变量:

Groovy
1
2
def methodName = 'listArgs'
new Person()."$methodName"()."$methodName"()

构造Groovy对象,除了使用Java的构造器调用外,还有更多方式:

Groovy
1
2
3
4
5
6
//as关键字进行强制转型列表为对象,列表为构造器入参
Person p = ['Alex', 29] as Person
//使用命名参数的方式访问构造器
p = new Person(age : 29, name : 'Alex')
//隐式构造
Person p = ['Alex', 29]
空指针安全的引号操作符

在进行多级点号导航的访问时,一旦路径上的对象为null,就会导致NullPointerException(NPE)的出现。在Java中这需要很繁琐的处理,在入参规格无法确定的情况下,方法实现者可能需要进行多次空指针检查。

Groovy引入特殊的操作符 ?. 来允许安全的属性导航——当路径中出现null时,当前表达式的估算中止,并返回null。下面是一个例子:

Groovy
1
2
3
4
5
6
7
8
9
10
11
class Contact{
    class Address{
        class Tel{
            def zoneCode
        }
        Tel tel
    }
    Address address
}
def c = new Contact()
assert c?.address?.tel?.zoneCode == null
继承、接口和特征

Groovy完整的支持Java的继承和接口机制,并且两者的代码可以几乎完美的交互。

但是Groovy提供了更加动态的能力,你可以把Map或者Closure强制转型为目标接口:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Person
{
    def sayHello(String content);
}
interface Greeter extends Person
{
    def getName();
}
// 对于单方法接口,可以从Closure转型得到
Person p = { content -> println content } as Person
// 对于多方法接口,可以从Map转型得到
Greeter g = [
    sayHello : { content -> println content },
    getName : { 'Alex' }
] as Person
 
// 注意:仅仅调用的方法才需要实现,其它方法不实现也不影响编译

trait用于代替旧的Mixin机制,实现(而不是仅仅像接口那样声明)可共享的、可组合的行为,和Java 8 的Default method类似:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
trait HasId
{
    long id
}
trait HasVersion
{
    long version
}
trait Persistent
{
    boolean save()
    {
        println "saving ${this.dump()}"
    }
}
//trait与接口很类似,但是它可以包含字段、方法实现
trait Entity implements Persistent, HasId, HasVersion
{
    //覆盖父trait的行为
    boolean save()
    {
        version++
        Persistent.super.save()
    }
}
//类可以实现trait,就像可以实现interface一样
class Publication implements Entity
{
    String title
}
class Book extends Publication
{
    String isbn
}
动态重载

我们知道,Java的重载是静态的——调用哪个重载版本在编译期就确定,依据方法入参的声明类型来决定链接到哪个方法:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class HelloJava
{
    public void m( Object o )
    {
        System.out.println( "Object" );
    }
 
    public void m( String s )
    {
        System.out.println( "String" );
    }
 
    public static void main( String[] args )
    {
        HelloJava hj = new HelloJava();
        Object o0 = new Object();
        Object o1 = new String();
        hj.m( o0 ); //Object
        hj.m( o1 ); //Object
    }
}

Groovy在这一点上与之完全相反,重载方法的选择是依据变量的运行时类型,动态决定的:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class HelloGroovy
{
    def m( Object o )
    {
        System.out.println( "Object" );
    }
 
    def  m( String s )
    {
        System.out.println( "String" );
    }
 
    static  main( String[] args )
    {
        HelloGroovy hg = new HelloGroovy();
        Object o0 = new Object();
        Object o1 = new String();
        hg.m( o0 ); //Object
        hg.m( o1 ); //String
    }
}
GroovyBean

前面已经提到过,Groovy会默认访问限定符的实例变量自动生成getter/setter。另外需要注意:

  1. 在类外部, ref.fieldName 会自动映射为getter/setter调用,要想规避这一点,必须使用特殊操作符: ref.@fieldName 
  2. 在类内部, ref.fieldName 直接访问字段

任何类都可以实现 getProperty(name) 和 setProperty(name,value) ,改变Bean属性访问时的默认逻辑。

高级语言特性
Gpath

类似于Xpath,Gpath是Groovy语言强大的对象导航结构,它引入一个展开点操作符: *. 。

对于列表来说: list*.property 等价于 list.collect{ item -> item?.property } ,下面是一段示例代码:

Groovy
1
2
3
4
5
6
7
8
9
println this //当前类:HelloGroovy@14a2f921
println this.class //调用getClass():class HelloGroovy
println this.class.methods //调用getClass().getMethod():很长的一个list
//*.操作符,对集合的每个元素调用.name
println this.class.methods*.name
//上述*.操作符可以简写为.
println this.class.methods.name
 
println this.class.methods.name.grep(~/get.*/).sort() //[getBinding, getClass, getMetaClass, getProperty]
集合展开(spread)操作符

可以使用 * 在当前位置把集合展开:

Groovy
1
2
3
4
5
6
7
8
9
10
11
def sum (a,b,c)
{
    a + b + c
}
assert 6 == sum( *[1, 2, 3])
 
def range = (1..3)
assert [0, 1, 2, 3] == [0, *range]
 
def map = [a:1,b:2]
assert [a:1, b:2, c:3] == [c:3, *:map]
use关键字

Groovy为Object类添加了一个 use() 方法,用于扩展/修改一个类的可用实例方法。use的入参是一种被称为Category的特殊类,这种类包含一组静态方法,use使得这些方法可以作为第一个入参的实例方法使用。下面是一个简单示例:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class StringPlusCategory
{
    static def plus(String self, String operand)
    {
        return self.toInteger() + operand.toInteger()
    }
}
assert '33' == '3' + '3'
//临时改变String的+操作符的行为
use(StringPlusCategory)
{
    assert 6 == '3' + '3'
}
 
assert '33' == '3' + '3'

需要注意的是,use导致的扩展/修改是临时的,不会产生全局性的影响。 

调用简写法与DSL

如果一个链式调用(chain-of-method)类似: link(producer).to(consumer) 

则Groovy允许你将其编写为: link producer to consumer 。这非常类似于自然语言的语法,在DSL(领域特定语言)中非常有用。下面是更多的例子:

Groovy
1
2
3
move(10, forward).painting(color:blue)
//支持多参数调用,支持命名参数
move 10, forward painting color:blue

Groovy还允许把作为最后一个入参的闭包置于括号外面,有时可以达到很奇特的效果:

Groovy
1
2
3
4
5
6
7
8
9
10
def when(expr,closure)
{
    if(expr) closure()
}
 
//这里好像我们自己发明了新的控制结构一样
when(true)
{
    println 'OK' ;
}
Groovy动态编程

所谓动态编程,是指在运行时修改对象结构(添加状态、改变行为)的编程风格,是动态语言的优势所在。

元对象协议

当Groovy调用方法时,它不会直接调用,而是总是把调用请求转交给一个中间层——MOP——处理。MOP由相应的API定义。其一系列规则,这些规则限定了:

  1. Groovy运行时如何处理方法调用请求
  2. 如何控制中间层

为了寻找正确的目标方法,MOP需要很多信息,这些信息存放在元类中。元类可以在运行时改变或者替换,从而改变对象的行为。

即使不改变元类,Groovy也允许通过一系列钩子方法改变对象的行为。

AST转换器
注解 说明
@groovy.transform.ToString 生成人类可读的toString()方法
@groovy.transform.EqualsAndHashCode 生成equals()和hashCode()方法
@groovy.transform.TupleConstructor

生成基于各字段组合的构造函数:

Groovy
1
2
3
4
5
@TupleConstructor(excludes=['lastName'])
class Person {
    String firstName
    String lastName
}
@groovy.transform.MapConstructor 生成入参为map的构造函数,键为字段名
@groovy.transform.Canonical ToString + EqualsAndHashCode + TupleConstructor
@groovy.transform.InheritConstructors 生成匹配父类构造器的构造器
@groovy.lang.Category 简化Category的编写
@groovy.transform.IndexedProperty

为数组、列表字段生成索引化的Getter/Setter:

Clojure
1
2
3
4
5
6
7
8
class SomeBean {
    @IndexedProperty String[] someArray = new String[2]
    @IndexedProperty List someList = []
}
 
def bean = new SomeBean()
bean.setSomeArray(0, 'value')
bean.setSomeList(0, 123)
@groovy.lang.Lazy

自动生成字段的延迟初始化逻辑:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
class SomeBean {
    @Lazy LinkedList myField
}
 
// 等价于
List $myField
List getMyField() {
    if ($myField!=null) { return $myField }
    else {
        $myField = new LinkedList()
        return $myField
    }
}
@groovy.lang.Newify

增加Python、Ruby风格的构造函数:

Clojure
1
2
3
4
5
6
7
8
9
@Newify([Tree,Leaf])
class TreeBuilder {
    Tree tree = Tree(Leaf('A'),Leaf('B'),Tree(Leaf('C')))
}
 
@Newify([Tree,Leaf])
class TreeBuilder {
    Tree tree = Tree.new(Leaf.new('A'),Leaf.new('B'),Tree.new(Leaf.new('C')))
}
@groovy.transform.Sortable 辅助编写Comparable类
@groovy.transform.builder.Builder

生成Fluent API:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Builder(builderStrategy=SimpleStrategy)
class Person {
    String first
    String last
    Integer born
}
def p1 = new Person().setFirst('Johnny').setLast('Depp').setBorn(1963)
 
 
@Builder(builderStrategy=SimpleStrategy, prefix="")
class Person {
    String first
    String last
    Integer born
}
def p = new Person().first('Johnny').last('Depp').born(1963)
类设计注解

这些注解用于简化设计模式的实现。

注解 说明
@groovy.transform.BaseScript 指定脚本需要继承自特定的自定义脚本,而非groovy.lang.Script
@groovy.lang.Delegate

代理模式:

Groovy
1
2
3
4
5
class Event {
    // 生成一个Date when字段,并将对Date方法的调用转发给when
    @Delegate Date when
    String title
}
@groovy.transform.Immutable 实现不变模式
@groovy.transform.TailRecursive 将尾递归转换为迭代风格,避免栈过深
@groovy.lang.Singleton

实现单例模式:

Groovy
1
2
3
4
5
6
@Singleton
class GreetingService {
    String greeting(String name) { "Hello, $name!" }
}
 
assert GreetingService.instance.greeting('Bob') == 'Hello, Bob!'
声明式并发
注解 说明
@groovy.transform.Synchronized

实现类似于synchronized的功能,但是可以针对不同对象进行锁定,默认情况下创建$lock、$LOCK(用于静态方法)锁对象

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Counter {
    int cpt
    @Synchronized
    int incrementAndGet() {
        cpt++
    }
}
 
// 生成
class Counter {
    int cpt
    private final Object $lock = new Object()
 
    int incrementAndGet() {
        synchronized($lock) {
            cpt++
        }
    }
}

 

@groovy.transform.WithReadLock   读写锁  
 @groovy.transform.WithWriteLock
简化克隆和串行化
注解 说明
@groovy.transform.AutoClone

辅助实现@java.lang.Cloneable接口,可以通过style自动克隆策略:

  1. AutoCloneStyle.CLONE,默认,先调用super.clone(),再针对每个可克隆属性调用clone()
  2. AutoCloneStyle.SIMPLE,调用构造器,并拷贝属性
  3. AutoCloneStyle.COPY_CONSTRUCTOR 创建并使用拷贝构造器
  4. AutoCloneStyle.SERIALIZATION,使用串行化机制克隆对象
@groovy.transform.AutoExternalize 指定外部化包括/排除的字段
编译器指令
注解 说明
@groovy.transform.Field

仅仅在脚本的上下文中有意义,将一个变量声明为脚本类的字段,而非run方法的局部变量

Groovy
1
@Field def x
@groovy.transform.PackageScope 包内可见的字段,不生成属性
@groovy.transform.AutoFinal  提示编译器自动插入final关键字
内嵌依赖管理
注解 说明
@Grab

添加一个依赖:

Groovy
1
2
@Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate

 也可以使用下面的简写法:

Groovy
1
@Grab('org.springframework:spring-orm:3.2.5.RELEASE')
@GrabResolver

指定仓库:

Groovy
1
@GrabResolver(name='restlet', root='http://maven.restlet.org/')
@GrabConfig 

使用系统类加载器加载:

Groovy
1
2
@GrabConfig(systemClassLoader=true)
@Grab(group='mysql', module='mysql-connector-java', version='5.1.6')
利用钩子方法定制MOP
处理缺失的方法和属性
钩子方法 说明 
methodMissing

调用任何对象不支持的方法,转交给该钩子处理:

Groovy
1
2
3
4
5
6
7
8
class Pretender
{
    Object methodMissing(String name, Object arguments){
        println "Method $name called"
    }
}
def p = new Pretender();
p.hello()
propertyMissing

与上面类似, 当访问不存在的属性时,交由该钩子处理,下面的例子演示了一个DSL风格的二进制计算器:

Groovy
1
2
3
4
5
6
7
8
9
10
11
def propertyMissing(String name)
{
    int result = 0
    name.each
    {
        result <<= 1
        if (it == 'I') result++
    }
    return result
}
assert IIOI + IOI == IOOIO
动态钩子

通过把逻辑委托给一个闭包,可以针对一个对象实例(而不是类),在运行时动态修改逻辑:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
class DynamicPretender
{
    Closure whatToDo = { name -> "accessed $name"} //默认钩子实现
    def propertyMissing(String name)
    {
        whatToDo(name)
    }
}
def one = new DynamicPretender()
assert one.hello == 'accessed hello'
//修改钩子实现
one.whatToDo = { name -> name.size() }
assert one.hello == 5
定制GroovyObject的方法

所有由Groovy类都实现了下面的接口(继承了 GroovyObjectSupport ):

groovy.lang.GroovyObject
Groovy
1
2
3
4
5
6
7
8
public interface GroovyObject
{
    Object invokeMethod(String methodName, Object args);
    Object getProperty(String propertyName);
    void setProperty(String propertyName, Object newValue);
    MetaClass getMetaClass();
    void setMetaClass(MetaClass metaClass);
}

任何该接口的子类,都遵守下面的规则:

  1. 对其属性的访问,委托给getter/setter
  2. 对任何不存在的方法的调用,委托给invokeMethod()
  3. 如果子类实现了GroovyInterceptable,那么对任何方法的调用委托给invokeMethod()

缺省适配 GroovyObjectSupport 的方法实现,通过委托调用给对象的元类,应用上述规则:

Groovy
1
2
3
4
public Object invokeMethod(String name, Object args)
{
    return getMetaClass().invokeMethod(this, name, args);
}
下面的例子演示了如何通过覆盖GroovyObject的方法,突破“只有非0参方法调用才允许省略括号”的限制:
Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class NoParens
{
    def getProperty(String propertyName)
    {
        if (metaClass.hasProperty(this, propertyName))
        {
            return metaClass.getProperty(this, propertyName)
        }
        invokeMethod propertyName, null
    }
}
class PropUser extends NoParens
{
    boolean existingProperty = true
}
 
def user = new PropUser()
assert user.existingProperty
//现在可以免括号调用0参方法了
assert user.toString() == user.toString
通过元类定制行为

上面演示的动态编程方式,都是“侵入性”的——需要修改目标类的源码。Groovy还允许修改元类本身,来实现“非侵入性”的动态编程。

元类简介

Groovy为任何一个类维护一个元类(metaclass)信息,元类使用类型 groovy.lang.MetaClass 表示。元类持有类的所有属性、方法信息,包括Groovy附加的方法。

一般情况下,类的所有实例共享一个元类,但是Groovy支持通过 setMetaClass( metaClass ) 修改单个实例的元类。

你可以很容易的和元类交互,例如:

Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
MetaClass mc = String.metaClass
final Object[] NO_ARGS = []
//检查方法的可用性
assert 1 == mc.respondsTo("toString", NO_ARGS).size()
//检查属性、方法的数量
assert 3 == mc.properties.size()
assert 74 == mc.methods.size()
//检查Groovy动态添加的元方法的数量
assert 176 == mc.metaMethods.size()
//调用方法、静态方法、构造器
assert "" == mc.invokeMethod("","toString", NO_ARGS)
assert null == mc.invokeStaticMethod(String, "println", NO_ARGS)
assert "" == mc.invokeConstructor(NO_ARGS)

默认情况下, getMetaClass() 通过元类注册表 MetaClassRegistry 来查找元类,除非你覆盖前述方法的实现。该注册表维护了类到元类的映射关系。

Groovy内置了若干元类实现:

  1. 默认实现 MetaClassImpl ,大部分情况下使用
  2. ExpandoMetaClass 用于扩展状态和行为
  3. ProxyMetaClass 装饰一个元类,作为拦截器使用
  4. 其它内部、测试用的元类
使用ProxyMetaClass
Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class InspectMe
{
    def method()
    {
    }
}
def tracer = new TracingInterceptor(writer: new StringWriter())
 
def proxyMetaClass = ProxyMetaClass.getInstance(InspectMe)
proxyMetaClass.interceptor = tracer //设置拦截器
 
InspectMe inspectMe = new InspectMe()
inspectMe.metaClass = proxyMetaClass //运行时修改元类
 
inspectMe.method()
println tracer.writer.toString()
使用ExpandoMetaClass

为元类添加状态/行为后,元类自动成为 ExpandoMetaClass ,类的所有实例获得新的状态/行为:

Groovy
1
2
3
4
5
assert String.metaClass =~ /MetaClassImpl/
//为元类添加方法
String.metaClass.low = {-> delegate.toLowerCase() } //闭包的delegate自动指定指向当前对象
assert String.metaClass =~ /ExpandoMetaClass/ //元类已经自动修改
assert "ALEX".low() == "alex"

使用 Expando 可以在对象级别上动态添加状态/行为:

Groovy
1
2
3
4
5
6
class Person extends Expando
{}
def person = new Person()
person.name = 'Alex'
person.sayHello = { -> println 'Hello ' + delegate.name }
person.sayHello()
修改元类
Groovy
1
2
3
4
5
6
7
8
9
10
11
12
//修改元类,同时添加多个状态/行为
String.metaClass
{
    shift = -1
    low {-> delegate.toLowerCase() }
}
assert "".shift == -1
assert "ALEX".low() == 'alex'
 
//修改元类,添加静态成员
Integer.metaClass.static.answer = {-> 42}
assert Integer.answer() == 42
← 天空の城ラピュタ
Next Post →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • 基于Eclipse和Maven的Groovy开发
  • 基于nGrinder进行负载测试
  • Maven POM文件配置示例
  • Spring Cloud学习笔记
  • SpringMVC知识集锦

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • Bazel学习笔记 37 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2