Groovy

作者: 坚持到底v2 | 来源:发表于2018-03-26 17:38 被阅读0次

    官方文档

    http://www.groovy-lang.org/documentation.html
    http://docs.groovy-lang.org/latest/html/documentation/core-metaprogramming.html#_groovyobject_interface

    IO: https://www.jianshu.com/p/5732fc8b49e5
    Collection: https://www.jianshu.com/p/67f78abd7242
    Runtime and compile-time metaprogramming: https://www.jianshu.com/p/b0ebb5c5e562
    Testing guide: https://www.jianshu.com/p/8c3afa45c7c7
    Integrating Groovy into applications: https://www.jianshu.com/p/40106170745e
    Design patterns in Groovy: https://www.jianshu.com/p/005c412c8ae3
    Parsing and producing JSON: https://www.jianshu.com/p/3c28985dc872


    一、环境搭建

    Linux下就是下载一个tar包,解压,使用 bin/ 目录中的 groovygroovycgroovysh 命令
    或者安装后可以直接使用这些命令

    Windows下推荐使用eclipse插件
    参考官方文档 https://github.com/groovy/groovy-eclipse/wiki
    或者直接下载插件包离线安装


    二、语法

    1、注释

    1.1、单行注释和多行注释

    和Java类似

    // a standalone single line comment
    println "hello" // a comment till the end of the line
    
    /* a standalone multiline comment
       spanning two lines */
    println "hello" /* a multiline comment starting
                       at the end of a statement */
    println 1 /* one */ + 2 /* two */
    

    1.2、GroovyDoc 注释

    和java类似

    /**
     * A Class description
     */
    class Person {
        /** the name of the person */
        String name
    
        /**
         * Creates a greeting method for a certain person.
         *
         * @param otherPerson the person to greet
         * @return a greeting message
         */
        String greet(String otherPerson) {
           "Hello ${otherPerson}"
        }
    }
    

    1.3、Shebang line

    #!/usr/bin/env groovy
    println "Hello from the shebang line"
    

    2、Identifiers

    不能以数字开头
    不能是表达式

    2.1、Quoted identifiers

    引号引起来的标识符
    它出现于 . 表达式的后面。
    例如,person.name 可以使用 person."name" 表达式表示
    当属性包含空格和其他不合法字符串时,这非常有趣

    例如

    def map = [:]
    
    map."an identifier with a space and double quotes" = "ALLOWED"
    map.'with-dash-signs-and-single-quotes' = "ALLOWED"
    
    assert map."an identifier with a space and double quotes" == "ALLOWED"
    assert map.'with-dash-signs-and-single-quotes' == "ALLOWED"
    

    这种用法还可以用于动态指定key,例如

    map.'single quote'
    map."double quote"
    map.'''triple single quote'''
    map."""triple double quote"""
    map./slashy string/
    map.$/dollar slashy string/$
    
    
    def firstname = "Homer"
    map."Simpson-${firstname}" = "Homer Simpson"
    
    assert map.'Simpson-Homer' == "Homer Simpson"
    

    3、字符串

    Groovy允许你实例化 java.lang.String 对象,也可以是 GString ( groovy.lang.GString ), 即解释型的字符串。

    // 单引号引起来的字符串不支持解释
    'a single quoted string'
    '''line one
    line two'''
    
    // 字符串相加
    assert 'ab' == 'a' + 'b'
    
    // 如果你的代码是缩进的,你可以使用GDK的 String#stripIndent() 和 String#stripMargin() 方法,
    // 它接受一个分隔符来标识要从字符串开头处移除的文本
    
    //注意如下的方式创建字符串会在字符串开头包含一个\n
    def startingAndEndingWithANewline = '''
    line one
    line two
    line three
    '''
    // 你可以使用转义来消除
    def strippedFirstNewline = '''\
    line one
    line two
    line three
    '''
    
    assert !strippedFirstNewline.startsWith('\n')
    

    3.1、 转义特殊字符

    使用 \

    3.2、双引号引起来的字符串

    如果没有解释表达式,就是 java.lang.String 字符串
    如果有解释表达式,就是 groovy.lang.GString

    3.2.1、解释字符串

    使用 ${} 包含表达式,或者简单的使用 $
    ${} 可以是任意的表达式组合,并且取最后一个表达式返回的值嵌入到字符串中

    例如

    println "The sum of 1 and 2 is equal to ${def a = 1; def b = 2; a + b}"
    

    也可以只使用 $ 而不提供 {} ,但是如果这样使用,则表达式仅限于 a.ba.b.c 这样的格式
    但是包含方法调用括号、闭包花括号或算术运算符的表达式将是无效的

    例如

    def number = 3.14
    //下面的语句将抛出异常, 这是因为Groovy认为你正在尝试访问number的toString属性,而不是调用number的toString()方法
    println "$number.toString()"
    //你可以使用
    println "${number.toString()}"
    

    如果你需要转义 $${},只需要转义 $ 即可,即 \$

    3.2.2、延迟解释

    ${->} 表示延迟解释,即表示这声明了一个真正的 closure

    例如

    def number = 1 
    def eagerGString = "value == ${number}"
    def lazyGString = "value == ${ -> number }"
    
    assert eagerGString == "value == 1" 
    assert lazyGString ==  "value == 1" 
    
    number = 2 
    assert eagerGString == "value == 1" 
    assert lazyGString ==  "value == 2" 
    

    ${} 字符串也并非是一成不变,只是它仅仅表示指向的对象不变
    如下所示

    class Person {
        String name
        String toString() {
            name
        }
    }
    def sam = new Person(name:'Sam')
    def gs = "Name: ${sam}"
    assert gs == 'Name: Sam'
    sam.name = 'Lucy'
    assert gs == 'Name: Lucy'
    
    // 看到了吗, gs仅仅表示指向的对象 sam 不变,但是sam.name 属性变了,gs表示的字符串也会变化
    

    当一个方法(无论是使用Java实现还是Groovy实现)期望接收一个 java.lang.String 对象,但是我们传入了一个 groovy.lang.GString 对象进去,它会自动调用 groovy.lang.GStringtoString() 方法将其转换为 java.lang.String 对象

    3.2.3 GString 和 String 的 hashCodes

    虽然解释型字符串可以用来代替普通的Java字符串,但是它们的hashCode不同!!

    普通Java字符串是不可变的,而 GString 的结果字符串表示可以根据其内插值而变化。
    即使对于相同的结果字符串,GStringsStrings 具有相同的hashCode。

    例如:

    // 它们的hashCode不同
    assert "one: ${1}".hashCode() != "one: 1".hashCode()
    
    def key = "a"
    def m = ["${key}": "letter ${key}"]
    println m
    // 输出 [a:letter a]
    assert m["a"] == null
    
    m["${key}"]="new A"
    println m
    // 输出  [a:letter a, a:new A]
    assert m.get("a") == "new A"
    assert m.get("${key}") == "letter a"
    
    // 这是为什么呢?
    // 因为 map 初始化时使用的是 map.put(Object key,Object  value)方法,所以初始化时使用 GString ,key 是一个GString,而不是 String
    // 而使用下标索引时 m["a"]=m["${key}"]=m.getAt(String property) 使用的是 map.getAt(String prop)方法,索引的key是指定的字符串属性,
    // 所以你使用 m["a"]或m.getAt("a") 无法索引到GString 类型的key,你要索引它应该使用 m.get("${key}")
    
    // 同样,使用下标索引设置值时 m["${key}"]="new A" 等价于 m.putAt(String prop,Object value) , 
    // 该方法接收一个字符串对象,所以GString会自动转换为 String,
    // 而使用 m.put(Object key,Object value) 时,GString则不会转换为 String
    
    

    3.2.4 Slashy字符串

    使用 // 包裹的字符串
    用于定义正则表达式
    例如

    def fooPattern = /.*foo.*/
    assert fooPattern == '.*foo.*'
    
    def escapeSlash = /The character \/ is a forward slash/
    assert escapeSlash == 'The character / is a forward slash'
    

    注意空的slashy字符串不能使用//表示
    因为 // 是注释
    例如

    // 下面的语句会编译错误
    assert '' == //
    

    3.2.5 Dollar slashy字符串

    Dollar slashy字符串是指 $/ 开头,/$ 结尾的字符串
    转义符是 $ , 并且它可以转义另一个 $ , 或 / , 但是这不是必须的,除非必须转义。
    例如

    def name = "Guillaume"
    def date = "April, 1st"
    
    def dollarSlashy = $/
        Hello $name,
        today we're ${date}.
    
        $ dollar sign
        $$ escaped dollar sign
        \ backslash
        / forward slash
        $/ escaped forward slash
        $$$/ escaped opening dollar slashy
        $/$$ escaped closing dollar slashy
    /$
    
    assert [
        'Guillaume',
        'April, 1st',
        '$ dollar sign',
        '$ escaped dollar sign',
        '\\ backslash',
        '/ forward slash',
        '/ escaped forward slash',
        '$/ escaped opening dollar slashy',
        '/$ escaped closing dollar slashy'
    ].every { dollarSlashy.contains(it) }
    

    3.2.6 Characters

    不像Java,Groovy没有显式的字符声明格式,但是你可以显式将一个字符串声明为字符类型
    例如

    // 声明时指定类型
    char c1 = 'A' 
    assert c1 instanceof Character
    
    // 使用 as 声明
    def c2 = 'B' as char 
    assert c2 instanceof Character
    
    // 使用强转声明
    def c3 = (char)'C' 
    assert c3 instanceof Character
    
    

    4、数字

    4.1、整数类型

    和Java一样
    byte char short int long
    java.lang.BigInteger

    如果你不是显式的声明数字的类型(使用 def 声明数字),那么数字的类型取决于你声明的数字的大小,依次为 Integer、Long、BigInteger

    0b 开头声明数字表示使用二进制
    0 开头声明数字表示使用八进制
    0x 开头声明数字表示使用十六进制

    4.2 Decimal literals

    十进制数字,和Java一样
    float double
    java.lang.BigDecimal

    可以使用 eE , 例如

    assert 1e3  ==  1_000.0
    assert 2E4  == 20_000.0
    assert 3e+1 ==     30.0
    assert 4E-2 ==      0.04
    assert 5e-1 ==      0.5
    

    使用 def 声明十进制数字,默认使用的类型是 java.lang.BigDecimal
    不能使用 二进制、八进制、十六进制表示

    4.3 数字类型后缀

    使用 G/g 表示 BigInteger
    使用 L/l 表示 Long
    使用 I/i 表示 Integer
    使用 G/g 表示 BigDecimal
    使用 D/d 表示 Double
    使用 F/f 表示 Float

    除法/ 会产生一个 doubleBigDecimal 结果
    任何一个参数是 floatdouble 时,结果是 double
    其他情况时返回 BigDecimal 对象

    要想执行Java中的整数除法,使用 intdiv() 方法

    幂操作符** 产生的结果:
    基本上应该是 如果结果可以表示为 Integer/Long ,那就返回一个 Integer/Long 对象,
    否则返回 Double 对象或 BigDecimal 对象

    5、Lists

    Groovy使用 [,,,] 格式表示 lists
    Groovy list是普通的 java.util.List ,它没有定义自己的集合类
    定义列表文字时使用的具体列表实现默认为java.util.ArrayList,除非您决定另外指定
    例如

    def numbers = [1, 2, 3]         
    assert numbers instanceof List  
    assert numbers.size() == 3      
    
    def heterogeneous = [1, "a", true]  
    
    def arrayList = [1, 2, 3]
    assert arrayList instanceof java.util.ArrayList
    
    def linkedList = [2, 3, 4] as LinkedList    
    assert linkedList instanceof java.util.LinkedList
    
    LinkedList otherLinked = [3, 4, 5]          
    assert otherLinked instanceof java.util.LinkedList
    

    你可以使用 [] 下标来访问和设置列表元素
    使用 负数 的下标,可以从列表结尾向前索引
    使用 << 操作符追加元素到列表
    使用 [a,b,c,...][a..b] 同时访问数组的多个元素,并返回一个list, 其中 [a..b] 用法中 a可以比b大,并且会自动反向索引元素

    6、Arrays

    和 List类似,不过你需要显式地将其声明为数组类型

    String[] arrStr = ['Ananas', 'Banana', 'Kiwi']  
    
    assert arrStr instanceof String[]    
    assert !(arrStr instanceof List)
    
    def numArr = [1, 2, 3] as int[]      
    
    assert numArr instanceof int[]       
    assert numArr.size() == 3
    

    你还可以创建多维数组

    def matrix3 = new Integer[3][3]         
    assert matrix3.size() == 3
    
    Integer[][] matrix2                     
    matrix2 = [[1, 2], [3, 4]]
    assert matrix2 instanceof Integer[][]
    
    

    7、Maps

    上代码

    def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF']   
    
    assert colors['red'] == '#FF0000'    
    assert colors.green  == '#00FF00'    
    
    colors['pink'] = '#FF00FF'           
    colors.yellow  = '#FFFF00'           
    
    assert colors.pink == '#FF00FF'
    assert colors['yellow'] == '#FFFF00'
    
    assert colors instanceof java.util.LinkedHashMap
    

    Groovy使用 java.util.LinkedHashMap 创建 maps

    访问不存在的key会返回 null
    除了可以使用字符串作为key,还可以使用其他类型的对象作为key

    有时候你可能想使用变量声明map,但是你不能使用如下方式:

    def key = 'name'
    def person = [key: 'Guillaume']      
    
    // 不包含 name key
    assert !person.containsKey('name') 
    // 而是包含了字面值 key 作为key
    assert person.containsKey('key')     
    

    你应该使用下面的方式

    def key = 'name'
    person = [(key): 'Guillaume']      
    // 注意此时不能使用   
    // person = ["${key}": 'Guillaume'] 
    
    assert person.containsKey('name')    
    assert !person.containsKey('key')    
    
    

    三、操作符

    使用 intdiv() 执行整数除法, 除法/ 会产生一个 doubleBigDecimal 结果
    注意 ** 操作符返回的结果的类型

    三元操作符可以简化

    displayName = user.name ? user.name : 'Anonymous'   
    displayName = user.name ?: 'Anonymous'  
    

    1、对象操作符

    1.1、安全导航操作符

    防止访问空对象异常
    上代码

    def person = null
    //访问空对象的name返回null
    def name = person?.name 
    assert name == null 
    

    1.2、直接访问字段操作符

    我们调用对象的 someObj.someProp 时,实际上执行的是 someObj.getSomePropsomeObj.setSomeProp 方法,
    如果我们想直接访问对象的字段时,我们可以使用 someObj.@someProp

    例如:

    class User {
        public final String name                 
        User(String name) { this.name = name}
        String getName() { "Name: $name" }       
    }
    def user = new User('Bob')
    // 使用 user.name 等价于 user.getName()
    assert user.name == 'Name: Bob'
    assert user.@name == 'Bob'                   
    

    1.3、Method pointer 操作符

    使用 .& 来表示存储一个相关的方法到变量,以便日后调用它
    这种用法可以将函数变为 Closure 对象

    上代码

    def str = 'example of method reference'            
    def fun = str.&toUpperCase
    // 调用fun()方法,而不是fun变量
    // fun 实际上是一个 groovy.lang.Closure 对象
    def upper = fun()  
    
    assert upper == str.toUpperCase() 
    
    

    2、正则表达式操作符

    2.1、Pattern操作符

    // 创建一个 java.util.regex.Pattern 对象
    def p = ~/foo/
    assert p instanceof Pattern
    
    

    2.2、Find操作符

    def text = "some text to match"
    // =~ 创建一个 matcher,其类型为 java.util.regex.Matcher 
    def m = text =~ /match/                                           
    assert m instanceof Matcher                                       
    if (!m) {                                                         
        throw new RuntimeException("Oops, text not found!")
    }
    
    

    2.3、Match操作符

    // 使用 ==~ 直接判断是否match到
    m = text ==~ /match/                                              
    assert m instanceof Boolean                                       
    if (m) {                                                          
        throw new RuntimeException("Should not reach that point!")
    }
    

    3、其他操作符

    3.1、 Spread operator

    *. 操作符,用于索引集合内所有元素的属性,形成一个新的集合

    例如

    class Car {
        String make
        String model
    }
    def cars = [
           new Car(make: 'Peugeot', model: '508'),
           new Car(make: 'Renault', model: 'Clio')]       
    def makes = cars*.make                                
    assert makes == ['Peugeot', 'Renault']   
    
    

    该操作符是 null-safe 的,这意味着,当集合内的对象为 null 时,它在相应的位置也是 null ,而不是抛出异常

    cars = [
       new Car(make: 'Peugeot', model: '508'),
       null,                                              
       new Car(make: 'Renault', model: 'Clio')]
    assert cars*.make == ['Peugeot', null, 'Renault']     
    assert null*.make == null  
    
    

    *. 操作符可以应用于任何实现了 Iterable 接口的对象

    3.1.1、Spreading method arguments

    有时你的方法需要多个参数。
    此时,你可以使用 spread 操作符来调用方法。

    例如

    int function(int x, int y, int z) {
        x*y+z
    }
    
    def args = [4,5,6]
    assert function(*args) == 26
    // 设置可以
    args = [4]
    assert function(*args,5,6) == 26
    
    

    3.1.2、Spread list elements

    def items = [4,5] 
    def list = [1,2,3,*items,6] 
    assert list == [1,2,3,4,5,6] 
    

    3.1.3、Spread map elements

    def m1 = [c:3, d:4] 
    def map = [a:1, b:2, *:m1] 
    assert map == [a:1, b:2, c:3, d:4] 
    

    3.2、 Range operator

    .. 操作符

    例子

    def range = 0..5                                    
    assert (0..5).collect() == [0, 1, 2, 3, 4, 5]       
    assert (0..<5).collect() == [0, 1, 2, 3, 4]         
    // groovy.lang.Range 对象实现了 List接口
    assert (0..5) instanceof List                       
    assert (0..5).size() == 6                           
    
    

    3.3、 Spaceship operator

    <=> 操作符等价于 compareTo 方法

    上代码

    assert (1 <=> 1) == 0
    assert (1 <=> 2) == -1
    assert (2 <=> 1) == 1
    assert ('a' <=> 'z') == -1
    
    

    3.4、 Subscript operator

    [index] 等价于 getAtputAt

    上代码

    def list = [0,1,2,3,4]
    assert list[2] == 2                         
    list[2] = 4                                 
    assert list[0..2] == [0,1,4]                
    list[0..2] = [6,6,6]                        
    assert list == [6,6,6,3,4]  
    

    这意味着,你可以重写或实现 putAtgetAt 方法,然后就可以调用你的对象的下标方法

    3.5、 Membership operator

    in 操作符 等价于 isCase 方法。

    在List的上下文中,等价于 contains 方法

    3.6、 Identity operator

    在Groovy中, == 等价于 equals

    如果你想执行Java中的 ==,使用 is 方法

    上代码

    def list1 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3']        
    def list2 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3']        
    assert list1 == list2                                       
    assert !list1.is(list2)  
    
    

    3.7、 Coercion operator

    当你使用如下的方式强制类型时,会产生一个 ClassCastException 运行时错误

    Integer x = 123
    // 抛出 ClassCastException 异常
    String s = (String) x
    

    你可以使用 as操作符

    Integer x = 123
    String s = x as String
    

    当一个对象被强制为某一个类型时,除非目标类型和源类型相同,否则会产生一个新对象
    强制类型的规则取决于源类型和目标类型,并且有可能失败。
    自定义的 转换规则 可以通过 asType 方法实现

    3.8、 Diamond operator

    <> 操作符是一个纯语法糖操作符,用于支持与Java 7中同名操作符的兼容性。

    3.9 Call operator

    () 操作符 等价于 call 方法。
    只要实现了 call 方法,你就可以省略 .call 部分,例如

    class MyCallable {
        int call(int x) {           
            2*x
        }
    }
    
    def mc = new MyCallable()
    assert mc.call(2) == 4          
    assert mc(2) == 4 
    

    4、操作符重载

    image.png

    四、程序结构

    默认的imports:

    import java.lang.*
    import java.util.*
    import java.io.*
    import java.net.*
    import groovy.lang.*
    import groovy.util.*
    import java.math.BigInteger
    import java.math.BigDecimal
    

    带别名的import

    import java.util.Date
    import java.sql.Date as SQLDate
    
    Date utilDate = new Date(1000L)
    SQLDate sqlDate = new SQLDate(1000L)
    
    

    1. Scripts versus classes

    Groovy 支持 script 和 classes

    1.1 Groovy编译器会为你将 script 编译到 classes

    它会将 脚本的body 拷贝到一个run方法中。
    例如,编译后的类可能是下面的样子:

    import org.codehaus.groovy.runtime.InvokerHelper
    class Main extends Script {                     
        def run() {                                 
            println 'Groovy world!'                 
        }
        static void main(String[] args) {           
            InvokerHelper.runScript(Main, args)     
        }
    }
    

    如果你在脚本中声明了方法,那么方法也会被编译。
    所以,在脚本中,你可以不用在意方法声明的位置

    1.2 变量

    // 使用类型修订符声明的变量是 local variable。
    // 它会被声明在 run 方法中,并且不被外部可见
    int x=1
    
    // 直接定义变量会进入 script binding。binding对外部可见
    y=2
    
    // 如果你想将变量作为类变量,使用 @Field Annotation
    import groovy.transform.Field
    
    @Field def x=1
    

    例如:

    def x
    
    String line() {
        "="*x
    }
    
    x=3
    assert "===" == line()
    x=5
    assert "=====" == line()
    
    

    上面的代码会运行出错,这是因为,编译后的代码是

    class MyScript extends Script {
    
        String line() {
            "="*x
        }
    
        public def run() {
            def x
            x=3
            assert "===" == line()
            x=5
            assert "=====" == line()
        }
    }
    
    

    可以看到 变量x 在run方法内部,line方法无法访问到 变量x
    你可以使用 @Field Annotation

    import groovy.transform.Field
    @Field def x
    
    String line() {
        "="*x
    }
    
    x=3
    assert "===" == line()
    x=5
    assert "=====" == line()
    

    这样编译后的代码是

    class MyScript extends Script {
    
        def x
    
        String line() {
            "="*x
        }
    
        public def run() {
            x=3
            assert "===" == line()
            x=5
            assert "=====" == line()
        }
    }
    
    

    五、面向对象

    获取对象的 properties

    class Person {
        String name
        int age
    }
    def p = new Person()
    assert p.properties.keySet().containsAll(['name', 'age'])
    println p.properties
    // 输出 [class:class Person, age:0, name:null]
    

    1、 Traits

    Traits 是一种语言结构形式,它允许

    • 行为的组合
    • 运行时的接口实现
    • 行为覆盖
    • 兼容静态类型检查和编译

    它可以被视为 接口 ,并且包含了默认实现和状态。

    1.1、 在Traits中声明方法

    例子

    trait FlyingAbility {                           
            String fly() { "I'm flying!" }          
    }
    
    
    class Bird implements FlyingAbility {}          
    def b = new Bird()                              
    assert b.fly() == "I'm flying!" 
    

    1.1.1 在Traits中声明抽象方法

    trait Greetable {
        abstract String name()                              
        String greeting() { "Hello, ${name()}!" }           
    }
    
    class Person implements Greetable {    
        // 必须实现该抽象方法
        String name() { 'Bob' }                             
    }
    
    def p = new Person()
    assert p.greeting() == 'Hello, Bob!'  
    
    

    1.1.2 在Traits中声明private方法

    在Traits中声明的private方法在外部不可访问

    1.2 在Traits中this的含义

    this代表实现类的实例
    例如

    trait Introspector {
        def whoAmI() { this }
    }
    class Foo implements Introspector {}
    def foo = new Foo()
    assert foo.whoAmI().is(foo)
    

    ...

    1.3 陷阱

    我们已经看到了 Traits是有状态的。
    Traits可以定义 字段或 properties,但是当类实现Traits,它会在per-Traits基础上获取这些字段或 properties

    如下面的例子

    trait IntCouple {
        int x = 1
        int y = 2
        int sum() { x+y }
    }
    
    
    class BaseElem implements IntCouple {
        int f() { sum() }
    }
    def base = new BaseElem()
    assert base.f() == 3
    
    
    class Elem implements IntCouple {
        int x = 3                                       
        int y = 4                                       
        int f() { sum() }                               
    }
    def elem = new Elem()
    assert elem.f() == 3
    
    

    对于 BaseElem 来说, base.f()==3 这没问题
    但是对于 Elem , elem.f()==3 这是怎么回事?
    这是因为 sum 函数 访问的是 trait中的字段,如果你想使用 实现类中的值,你需要使用 getters 和 setters
    如下所示

    trait IntCouple {
        int x = 1
        int y = 2
        int sum() { getX()+getY() }
    }
    
    class Elem implements IntCouple {
        int x = 3                                       
        int y = 4                                       
        int f() { sum() }                               
    }
    def elem = new Elem()
    assert elem.f() == 7
    
    

    六、Closures

    1、 语法

    1.1 定义closure

    { [closureParameters -> ] statements }
    

    下面都是合法的定义

    { item++ } 
    
    { -> item++ } 
    
    // 默认变量名称为 it
    { println it } 
    
    { it -> println it } 
    
    { name -> println name } 
    
    { String x, int y -> 
        println "hey ${x} the value is ${y}"
    }
    
    { reader -> 
        def line = reader.readLine()
        line.trim()
    }
    

    closure可以作为一个变量,其类型为 groovy.lang.Closure

    调用 closure:

    def code = { 123 }
    assert code() == 123
    assert code.call() == 123
    
    
    def isOdd = { int i -> i%2 != 0 }                           
    assert isOdd(3) == true                                     
    assert isOdd.call(2) == false                               
    
    def isEven = { it%2 == 0 }                                  
    assert isEven(3) == false                                   
    assert isEven.call(2) == true             
    

    2、Delegation strategy (代理策略)

    2.1 Groovy closures vs lambda表达式

    它们不一样

    2.2 Owner、delegate and this

    this 对应于声明 closure 的类对象,等价于使用 getThisObject()
    owner 对应于声明 closure 的对象,它可能是一个类或一个closure,等价于使用 getOwner()
    delegate 对应于调用closure的对象,等价于是 getDelegate()

    delegate可以被更改为任意对象。

    例如

    class Person {
        String name
    }
    class Thing {
        String name
    }
    
    def p = new Person(name: 'Norman')
    def t = new Thing(name: 'Teapot')
    
    def upperCasedName = { delegate.name.toUpperCase() }
    
    upperCasedName.delegate = p
    assert upperCasedName() == 'NORMAN'
    upperCasedName.delegate = t
    assert upperCasedName() == 'TEAPOT'
    
    

    2.3、 Delegation strategy

    在一个closure中,当一个 property 没有显式指定给某个receiver时,此时就引入了 delegation strategy

    例如

    class Person {
        String name
    }
    def p = new Person(name:'Igor')
    
    // name 没有关联的变量
    def cl = { name.toUpperCase() } 
    // 我们可以更改 cl的delegate为Person对象
    cl.delegate = p 
    // 然后该方法就可以成功调用
    assert cl() == 'IGOR' 
    

    上面的代码能成功执行的原因是 name 属性在 delegate 对象上透明地解析!

    closure实际上定义了多种 resolution strategies 供你选择:

    • Closure.OWNER_FIRST : 这是默认策略,优先使用owner的属性/方法
    • Closure.DELEGATE_FIRST :优先使用 delegate的属性/方法
    • Closure.OWNER_ONLY
    • Closure.DELEGATE_ONLY
    • Closure.TO_SELF : 只使用 this 对象的属性/方法,这一般是在自定义Closure的实现中才会用到

    例子:

    class Person {
        String name
        def pretty = { "My name is $name" }             
        String toString() {
            pretty()
        }
    }
    class Thing {
        String name                                     
    }
    
    def p = new Person(name: 'Sarah')
    def t = new Thing(name: 'Teapot')
    
    assert p.toString() == 'My name is Sarah'           
    p.pretty.delegate = t
    // 虽然更改了 delegate,但是不会改变 closure 的执行结果
    // 因为默认的 策略是 Closure.OWNER_FIRST, 他仍然会使用 owner的属性
    assert p.toString() == 'My name is Sarah'           
    
    // 更改策略后,结果就会发生变化
    p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
    assert p.toString() == 'My name is Teapot'
    

    3、 Functional programming

    3.1 Currying

    Groovy 中的 currying 允许你设置Closure中的一些参数的值,并返回一个新的closure以便接收更少的 参数

    3.1.1 left Currying

    上代码

    def nCopies = { int n, String str -> str*n }    
    def twice = nCopies.curry(2)                    
    assert twice('bla') == 'blabla'                 
    assert twice('bla') == nCopies(2, 'bla')        
    
    

    3.1.2 right Currying

    上代码

    def nCopies = { int n, String str -> str*n }    
    def blah = nCopies.rcurry('bla')                
    assert blah(2) == 'blabla'                      
    assert blah(2) == nCopies(2, 'bla')                   
    
    

    3.1.3 Index based currying

    上代码

    def volume = { double l, double w, double h -> l*w*h }      
    // 设置第2个参数(index从0开始)为2d
    def fixedWidthVolume = volume.ncurry(1, 2d)                 
    assert volume(3d, 2d, 4d) == fixedWidthVolume(3d, 4d)     
    
    // 从第2个参数开始,设置多个参数的值
    def fixedWidthAndHeight = volume.ncurry(1, 2d, 4d)          
    assert volume(3d, 2d, 4d) == fixedWidthAndHeight(3d)                     
    
    

    3.2 Memorization

    上代码

    def fib
    fib = { long n -> n<2?n:fib(n-1)+fib(n-2) }
    assert fib(15) == 610 // slow!
    
    // 因为计算 fib(15) 需要 fib(14) 和 fib(13)
    // 计算 fib(14) 又需要 fib(13) 和 fib(12)
    
    fib = { long n -> n<2?n:fib(n-1)+fib(n-2) }.memoize()
    assert fib(25) == 75025 // fast!
    
    // cache 使用 参数的实际值作为key . 
    // 这意味着当你使用非包装类型或原始类型的时使用 memoize() 时要注意
    
    

    3.3 Composition

    上代码

    def plus2  = { it + 2 }
    def times3 = { it * 3 }
    
    def times3plus2 = plus2 << times3
    assert times3plus2(3) == 11
    assert times3plus2(4) == plus2(times3(4))
    
    def plus2times3 = times3 << plus2
    assert plus2times3(3) == 15
    assert plus2times3(5) == times3(plus2(5))
    
    // reverse composition
    assert times3plus2(3) == (times3 >> plus2)(3)
    
    

    3.3 Trampoline

    递归算法经常被 stack 限制。
    例如经常会收到 StackOverflowException 异常

    Closure被包装在一个 TrampolineClosure 对象中。
    被调用时, a trampolined Closure 会调用原始的 Closure 并等待其结果
    如果调用的结果是另一个 TrampolineClosure 实例,则将再次调用这个Closure,直到返回 Closure 之外的值。
    这个值将称为 trampoline 的最终结果,这样就实现了 serially call, 而不是 filling the stack

    例子:

    def factorial
    factorial = { int n, def accu = 1G ->
        if (n < 2) return accu
        factorial.trampoline(n - 1, n * accu)
    }
    factorial = factorial.trampoline()
    
    assert factorial(1)    == 1
    assert factorial(3)    == 1 * 2 * 3
    assert factorial(1000) // == 402387260.. plus another 2560 digits
    
    

    七、Semantics

    1、Statements

    1.1 变量定义

    def o

    def 是类型名称的替代。
    在声明变量时,使用 def 表明你不关心变量的类型,你可以认为 defObject 的别名
    在声明变量时,你可以必须类型名称或使用 def ,这是为了使Groovy解析器可以检测到这是声明变量

    1.2 变量赋值

    // 多个变量同时赋值
    def (a,b,c) = [10,20,'foo']
    assert a == 10 && b == 20 && c == 'foo'
    
    def (int i, String j) = [10, 'foo']
    assert i == 10 && j == 'foo'
    
    def nums = [1, 3, 5]
    def a, b, c
    (a, b, c) = nums
    assert a == 1 && b == 3 && c == 5
    
    def (_, month, year) = "18th June 2009".split()
    assert "In $month of $year" == 'In June of 2009'
    
    // 如果左手边的变量多的话,多余的变量不会被赋值
    def (a, b, c) = [1, 2]
    assert a == 1 && b == 2 && c == null
    
    // 如果右手边的值多的话,多余的值会被忽略
    def (a, b) = [1, 2, 3]
    assert a == 1 && b == 2
    
    

    任何的对象只要实现了 getAt 方法,都可以用于多变量赋值
    例如

    @Immutable
    class Coordinates {
        double latitude
        double longitude
    
        double getAt(int idx) {
            if (idx == 0) latitude
            else if (idx == 1) longitude
            else throw new Exception("Wrong coordinate index, use 0 or 1")
        }
    }
    
    def coordinates = new Coordinates(latitude: 43.23, longitude: 3.67) 
    
    def (la, lo) = coordinates 
    
    assert la == 43.23 
    assert lo == 3.67
    
    

    1.3 控制语句

    1.3.1 条件语句

    Java中的 if / else

    Java中的 switch / case
    Groovy 中的 switch 可以处理任意类型的switch value 和不同类型的match
    例如

    def x = 1.23
    def result = ""
    
    switch ( x ) {
        case "foo":
            result = "found foo"
            // lets fall through
    
        case "bar":
            result += "bar"
    
        case [4, 5, 6, 'inList']:
            result = "list"
            break
    
        case 12..30:
            result = "range"
            break
    
        // 当变量是 instanceof Integer 时匹配
        case Integer:
            result = "integer"
            break
    
        case Number:
            result = "number"
            break
    
        // toString() representation of x matches the pattern?
        case ~/fo*/: 
            result = "foo regex"
            break
        
        // Closure 返回结构为true时匹配
        case { it < 0 }: // or { x < 0 }
            result = "negative"
            break
    
        default:
            result = "default"
    }
    
    assert result == "number"
    
    

    1.3.2 循环结构

    Java中标准的 for

    Groovy 中的 for
    例子

    // iterate over a range
    def x = 0
    for ( i in 0..9 ) {
        x += i
    }
    assert x == 45
    
    // iterate over a list
    x = 0
    for ( i in [0, 1, 2, 3, 4] ) {
        x += i
    }
    assert x == 10
    
    // iterate over an array
    def array = (0..4).toArray()
    x = 0
    for ( i in array ) {
        x += i
    }
    assert x == 10
    
    // iterate over a map
    def map = ['abc':1, 'def':2, 'xyz':3]
    x = 0
    for ( e in map ) {
        x += e.value
    }
    assert x == 6
    
    // iterate over values in a map
    x = 0
    for ( v in map.values() ) {
        x += v
    }
    assert x == 6
    
    // iterate over the characters in a string
    def text = "abc"
    def list = []
    for (c in text) {
        list.add(c)
    }
    assert list == ["a", "b", "c"]
    
    

    1.3.3 assertion

    assert [left expression] == [right expression] : (optional message)
    

    2、表达式

    2.1 GPath表达式

    GPath是Groovy集成的路径表达式,它可以用于标识结构化数据的nested parts
    GPath一般用于处理XML,但是它可以应用于任意的对象
    例如:

    • a.b.c 对于XML来说,会返回 a中的b中的c元素
    • a.b.c 对于POJOs来说,会返回 a.getB().getC()

    2.1.1 Object navigation

    void aMethodFoo() { println "This is aMethodFoo." } 
    
    assert ['aMethodFoo'] == this.class.methods.name.grep(~/.*Foo/)
    
    // this.class.methods.name.grep(~/.*Foo/) 等价于下面的代码
    List<String> methodNames = new ArrayList<String>();
    for (Method method : this.getClass().getMethods()) {
       methodNames.add(method.getName());
    }
    // 后面再用grep 过滤
    
    

    2.1.2 GPath for XML navigation

    3、 Promotion and coercion

    3.1 Closure to type coercion

    3.1.1 将Closure指定为SAM type

    SAM type是指 Single Abstract Method,即只定义了一个抽象方法的类型。
    这包括如下

    // 单方法的接口
    interface Predicate<T> {
        boolean accept(T obj)
    }
    
    // 只有一个抽象方法的抽象类
    abstract class Greeter {
        abstract String getName()
        void greet() {
            println "Hello, $name"
        }
    }
    
    // 你可以使用 as 操作符将Closure converted into a SAM type
    Predicate filter = { it.contains 'G' } as Predicate
    assert filter.accept('Groovy') == true
    
    Greeter greeter = { 'Groovy' } as Greeter
    greeter.greet()
    
    // 甚至你可以省略 as Type
    Predicate filter = { it.contains 'G' }
    assert filter.accept('Groovy') == true
    
    Greeter greeter = { 'Groovy' }
    greeter.greet()
    
    // 这意味着你也可以使用 method pointers
    boolean doFilter(String s) { s.contains('G') }
    
    Predicate filter = this.&doFilter
    assert filter.accept('Groovy') == true
    
    Greeter greeter = GroovySystem.&getVersion
    greeter.greet()
     
    

    3.1.2 Calling a method accepting a SAM type with a closure

    调用接收一个SAMtype作为参数的方法,然后使用 Closure来代替 SAM type
    假设如下方法

    public <T> List<T> filter(List<T> source, Predicate<T> predicate) {
        source.findAll { predicate.accept(it) }
    }
    
    // 你可以使用如下方式调用
    assert filter(['Java','Groovy'], { it.contains 'G'} as Predicate) == ['Groovy']
    // 甚至可以省略 as Type
    assert filter(['Java','Groovy']) { it.contains 'G'} == ['Groovy']
    
    

    3.1.3 Closure to arbitrary type coercion

    除了SAM types,Closure还可以被强制为任意的类型
    例如

    interface FooBar {
        int foo()
        void bar()
    }
    def impl = { println 'ok'; 123 } as FooBar
    
    assert impl.foo() == 123
    impl.bar()
    
    // 除了可以作为接口外,也可以作为类
    class FooBar {
        int foo() { 1 }
        void bar() { println 'bar' }
    }
    
    def impl = { println 'ok'; 123 } as FooBar
    
    assert impl.foo() == 123
    impl.bar()
    

    3.2 Map to type coercion

    一般来说,使用一个单独的closure实现一个接口或类 不是一个好方式。
    所以,groovy允许我们将一个map强制为一个接口或类。map的keys被解释为方法名称,values被解释为方法实现。
    例如

    def map
    map = [
      i: 10,
      hasNext: { map.i > 0 },
      next: { map.i-- },
    ]
    def iter = map as Iterator
    
    // 当调用map中不存在的方法时,会抛出 MissingMethodException  或 UnsupportedOperationException 异常
    // 如下所示
    interface X {
        void f()
        void g(int n)
        void h(String s, int n)
    }
    
    x = [ f: {println "f called"} ] as X
    x.f() // method exists
    x.g() // MissingMethodException here
    x.g(5) // UnsupportedOperationException here
    

    3.3 字符串到Enum强制转换

    上代码

    enum State {
        up,
        down
    }
    
    State st = 'up'
    assert st == State.up
    
    // 字符串不存在于enum中时,会抛出 IllegalArgumentException 运行时异常
    State st = 'not an enum value'
    
    

    3.4 自定义的类型强转

    实现 asType 方法

    上代码

    class Polar {
        double r
        double phi
        def asType(Class target) {
          if (Cartesian==target) {
            return new Cartesian(x: r*cos(phi), y: r*sin(phi))
          }
        }
    }
    class Cartesian {
       double x
       double y
    }
    
    
    def sigma = 1E-16
    def polar = new Polar(r:1.0,phi:PI/2)
    def cartesian = polar as Cartesian
    assert abs(cartesian.x-sigma) < sigma
    
    

    也可以将 asType 放在 Polar 类定义外面,使用 metaClass 扩展 ( 个人不建议, 因为这是全局的, 但是可以只设置某个具体对象的 metaClass 只对该对象有效)

    Polar.metaClass.asType = { Class target ->
        if (Cartesian==target) {
            return new Cartesian(x: r*cos(phi), y: r*sin(phi))
        }
    }
    
    

    3.5 Class literals vs variables and the as operator

    interface Greeter {
        void greet()
    }
    
    // Greeter is known statically, 此时使用 as
    def greeter = { println 'Hello, Groovy!' } as Greeter 
    greeter.greet()
    
    Class clazz = Class.forName('Greeter')
    // 此时不能使用 as, 因为 as 关键字只工作于 class literals
    greeter = { println 'Hello, Groovy!' } as clazz
    // 取而代之的,你可以使用 asType 方法
    greeter = { println 'Hello, Groovy!' }.asType(clazz)
    greeter.greet()
    
    

    4、Optionality

    4.1 可选的括号() parentheses

    调用方法时省略括号: 如果方法至少有一个参数,并且没有歧义,那么可以省略括号
    例如

    println 'Hello World'
    def maximum = Math.max 5, 10
    
    // 下面的语句则不能省略括号
    println()
    println(Math.max(5, 10))
    
    

    4.2 可选的 分号 semicolons

    4.3 可选的 return 关键字

    4.4 可选的 public 关键字

    5、Groovy Truth

    true 表示

    • 布尔true、
    • 集合非空、
    • matcher匹配、
    • 迭代器和枚举的hasNext为true、
    • map非空、
    • 字符串长度不为0、
    • 数字非0、
    • 对象非null、
    • 对象的asBoolean()方法返回true

    6、类型

    6.1 可选类型

    使用 def 动态推断对象类型

    6.2 静态类型检查

    使用 @groovy.lang.TypeChecked 注解类和方法 以激活类型检查
    当激活类型检查后,编译器会执行更多的工作:
    (1) 类型推断被激活,这意味着即使你在局部变量上使用def,类型检查器也能够从assignments中推断变量的类型
    (2) 方法调用在编译时解析,这意味着如果方法没有在类上声明,编译器会抛出一个错误
    (3) 一般来说,所有用于以静态语言查找的编译时错误都会显示:未找到方法,未找到属性,方法调用的不兼容类型,编号精度错误,...

    使用 @TypeChecked(TypeCheckingMode.SKIP) 注解 跳过类型检查

    6.2.1 类型检查assignments

    一个A类型的对象o可以被指定为T类型的变量,只要满足下列条件:
    (1) 对象类型A 和 变量类型T 一样: 例如 Date now = new Date()

    (2) 变量类型T是 String、boolean、Boolean或Class
    例如

    String s = new Date() // implicit call to toString
    Boolean boxed = 'some string'       // Groovy truth
    boolean prim = 'some string'        // Groovy truth
    Class clazz = 'java.lang.String'    // class coercion
    

    (3) 对象o是null,且变量类型T不是原始类型(例如int)

    (4) 变量类型T是 array,而且对象类型A也是array,并且A的组成类型可以被指定为T的组成类型
    例如 int[] i = new int[4] ; int[] i = [1,2,3]

    (5) 变量类型T 是 对象类型A的 superclass 或 变量类型T 是 对象类型A实现的接口

    (6) 变量类型T 或对象类型A 是原始类型和它们的包装类型

    (7) 变量类型T 是一个 SAM-type , 对象类型A是 groovy.lang.Closure 或其子类
    例如

    Runnable r = { println 'Hello' }
    
    interface SAMType {
        int doSomething()
    }
    SAMType sam = { 123 }
    assert sam.doSomething() == 123
    
    abstract class AbstractSAM {
        int calc() { 2* value() }
        abstract int value()
    }
    AbstractSAM c = { 123 }
    assert c.calc() == 246
    
    
    

    (8) 变量类型T 或对象类型A 是Number的子类并且它们满足相应的条件

    6.2.2 List and map constructors

    除了上面的规则之外,如果 assignment 在类型检查模式下被认为是无效的,那么如果满足以下条件,则可以将 list literal 或 map literal A 指派给 T类型的变量:
    (1) assignment 是一个变量声明,并且A是一个list,并且T的 constructor的参数匹配list中的元素的类型
    (2) assignment 是一个变量声明,并且A是一个map,并且T有一个no-arg constructor,并且map中的每个key都是其属性

    例如

    @groovy.transform.TupleConstructor
    class Person {
        String firstName
        String lastName
    }
    Person classic = new Person('Ada','Lovelace')
    
    // You can use a "list constructor":
    Person list = ['Ada','Lovelace']
    
    // or a "map constructor":
    Person map = [firstName:'Ada', lastName:'Lovelace']
    
    // map 中不能包含多余的key,否则会在编译时报异常
    

    6.2.3

    相关文章

      网友评论

          本文标题:Groovy

          本文链接:https://www.haomeiwen.com/subject/ebpxcftx.html