美文网首页
Groovy 知识点总结

Groovy 知识点总结

作者: 笑叶林 | 来源:发表于2018-06-22 16:10 被阅读797次

    Groovy是一门基于JVM的动态语言,很多语法和Java类似。大部分Java代码也同时是合法的Groovy代码。本文是快速入门,所以针对语法并不会做非常详细的介绍。如果需要详细语法,请直接查看Groovy官方文档。另外为了省事,本文中的大部分代码例子直接引用了Groovy文档。

    基本内容

    注释

    单行注释,以//开头。

    多行注释,/* */

    GroovyDoc注释,和JavaDoc注释类似。

    标识符

    大体上,Groovy标识符的命名规则和Java差不多。如果某个标识符在Groovy中合法,在Java中不合法,我们可以使用单引号或双引号将标识符包括起来。

    字符串

    字符串可以使用单引号'或双引号"包括起来。

    def str="1234"
    def str2='1234'
    

    多行字符串可以使用三个连续的单引号或双引号包括。不论是单行还是多行字符串, 都可以使用反斜杠转义字符。

    def multiline="""line1
    line2
    line3
    """
    

    我们还可以将变量直接插入到字符串中,这叫做内插字符串(String interpolation)。语法和EL表达式类似。编译器会把美元和花括号中的内容替换成实际的值,内插字符串中还可以进行表达式计算。

    def name = 'Guillaume' // a plain string
    def greeting = "Hello ${name}"
    

    当内插字符串可以由前后的符号区分出的时候,花括号可以省略。

    def person = [name: 'Guillaume', age: 36]
    assert "$person.name is $person.age years old" == 'Guillaume is 36 years old'
    

    当使用内插字符串的时候,字符串字面值是Groovy的字符串类型GString。这一点需要注意。普通的Java字符串是不变的,而GString是可变的。另外它们的哈希值也不同。因此在使用Map等数据类型的时候需要格外注意,避免使用GString作为Map的键。

    字符

    Groovy没有字符字面量。如果需要向Java方法传入单个字符的话,可以使用下面的方法进行转换。

    def c2 = 'B' as char 
    assert c2 instanceof Character
    
    def c3 = (char)'C' 
    assert c3 instanceof Character
    

    布尔类型

    Groovy的布尔类型和Java类似,也有truefalse两个值。不过Groovy的布尔语义更丰富。未到结尾的迭代器、非空对象引用、非零数字都认为是真;空集合、空字符串等认为是假。详情参见Groovy文档 core-semantics#Groovy-Truth

    数字类型

    Groovy支持bytecharshortintlongBigInteger等几种数字类型。如果使用普通方式声明,它们和Java中的变量很相似。

    int   i = 4
    
    BigInteger bi =  6
    

    如果使用def关键字声明,那么这些数字会自动选择可以容纳它们的类型。

    def a = 1
    assert a instanceof Integer
    
    // Integer.MAX_VALUE
    def b = 2147483647
    assert b instanceof Integer
    
    // Integer.MAX_VALUE + 1
    def c = 2147483648
    assert c instanceof Long
    
    // Long.MAX_VALUE
    def d = 9223372036854775807
    assert d instanceof Long
    
    // Long.MAX_VALUE + 1
    def e = 9223372036854775808
    assert e instanceof BigInteger
    

    这些整数还可以添加0b00x前缀,分别代表8进制数,8进制数和16进制数。

    另外Groovy还支持floatdoubleBigDecimal三种浮点数类型。原理同上。还可以使用科学计数法1.123E10这样的形式代表浮点数。

    Groovy的数字常量同样支持后缀区分字面值类型,这几种类型和Java中的类似。唯一不同的是Groovy还支持Gg后缀,代表BigInteger或BigDecimal类型,根据字面值是否含有小数点来区分。

    数学计算

    数字的计算结果和Java规则类似:小于int的整数类型会被提升为int类型,计算结果也是int类型;小于long的整数类型和long计算,结果是long类型;BigInteger和其它整数类型计算,结果是BigInteger类型;BigDecimal和其它整数类型计算,结果是BigDecimal类型;BigDecimal和float、double等类型计算,结果是double类型。

    列表

    Groovy中的列表比较灵活,有点像Python中的列表。使用[....]语法可以声明列表,默认情况下列表是ArrayList实现。我们也可以使用as运算符自己选择合适的列表底层类型。

    def arrayList = [1, 2, 3]
    assert arrayList instanceof java.util.ArrayList
    
    def linkedList = [2, 3, 4] as LinkedList    
    assert linkedList instanceof java.util.LinkedList
    

    有了列表之后,就可以使用它了。使用方法和Python差不多。我们使用[索引]引用和修改列表元素。如果索引是负的,则从后往前计数。要在列表末尾添加元素,可以使用左移运算符<<。如果在方括号中指定了多个索引,会返回由这些索引对应元素组成的新列表。使用两个点加首位索引..可以选择一个子列表。

    def letters = ['a', 'b', 'c', 'd']
    
    assert letters[0] == 'a'     
    assert letters[1] == 'b'
    
    assert letters[-1] == 'd'    
    assert letters[-2] == 'c'
    
    letters[2] = 'C'             
    assert letters[2] == 'C'
    
    letters << 'e'               
    assert letters[ 4] == 'e'
    assert letters[-1] == 'e'
    
    assert letters[1, 3] == ['b', 'd']         
    assert letters[2..4] == ['C', 'd', 'e']   
    

    列表还可以组合成复合列表。

    def multi = [[0, 1], [2, 3]]     
    assert multi[1][0] == 2   
    

    数组

    声明数组的方式和列表一样,只不过需要显示指定数组类型。数组的使用方法也和列表类似,只不过由于数组是不可变的,所以不能像数组末尾添加元素。

    int[] intArray = [1, 2, 3, 4, 5]
    
    def intArray2 = [1, 2, 3, 4, 5, 6] as int[]
    

    Map

    创建Map同样使用方括号,不过这次需要同时指定键和值了。Map创建好之后,我们可以使用[键].键来访问对应的值。默认情况下创建的Map是java.util.LinkedHashMap,我们可以声明变量类型或者使用as关键字改变Map的实际类型。

    def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF']   
    
    assert colors['red'] == '#FF0000'    
    assert colors.green  == '#00FF00' 
    

    关于Map有一点需要注意。如果将一个变量直接作为Map的键的话,其实Groovy会用该变量的名称作为键,而不是实际的值。如果需要讲变量的值作为键的话,需要在变量上添加小括号。

    def key = 'name'
    def person = [key: 'Guillaume']      //键是key而不是name
    
    assert !person.containsKey('name')   
    assert person.containsKey('key') 
    
    //这次才正确的将key变量的值作为Map的键
    person = [(key): 'Guillaume']        
    
    assert person.containsKey('name')    
    assert !person.containsKey('key')  
    

    运算符

    和Java类似的运算符

    Groovy的数学运算符和Java类似,只不过多了一个乘方运算**和乘方赋值**=

    Groovy的关系运算符(大于、小于等于这些)和Java类似。

    Groovy的逻辑运算符(与或非这些)和Java类似,也支持短路计算。

    Groovy的位运算符合Java类似。

    Groovy的三元运算符条件?值1:值2和Java类似。

    可空运算符

    Groovy支持Elvis操作符,当对象非空的时候结果是值1,为空时结果是值2。或者更直接,对象非空是使用对象本身,为空时给另一个值,常用于给定某个可空变量的默认值。

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

    安全导航运算符

    当调用一个对象上的方法或属性时,如果该对象为空,就会抛出空指针异常。这时候可以使用?.运算符,当对象为空时表达式的值也是空,不会抛出空指针异常。

    def person = Person.find { it.id == 123 }    
    def name = person?.name                      
    assert name == null
    

    字段访问运算符

    在Groovy中默认情况下使用点运算符.会引用属性的Getter或Setter。如果希望直接访问字段,需要使用.@运算符。

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

    方法指针运算符

    我们可以将方法赋给变量,这需要使用.&运算符。然后我们就可以像调用方法那样使用变量。方法引用的实际类型是Groovy的闭包Closure。这种运算符可以将方法作为参数,让Groovy语言非常灵活。

    def str = 'example of method reference'            
    def fun = str.&toUpperCase                         
    def upper = fun()                                  
    assert upper == str.toUpperCase()   
    

    展开运算符

    展开运算符*.会调用一个列表上所有元素的相应方法或属性,然后将结果再组合成一个列表。

    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值,不会抛出空指针异常,而是返回空值。

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

    展开运算符还可以用于展开方法参数、列表和Map。

    范围运算符

    使用..创建范围。默认情况下范围是闭区间,如果需要开闭区间可以在结束范围上添加<符号。范围的类型是groovy.lang.Range,它继承了List接口,也就是说我们可以将范围当做List使用。

    def range = 0..5                                    
    assert (0..5).collect() == [0, 1, 2, 3, 4, 5]       
    assert (0..<5).collect() == [0, 1, 2, 3, 4]         
    assert (0..5) instanceof List                       
    assert (0..5).size() == 6   
    

    比较运算符

    <=>运算符相当于调用compareTo方法。

    assert (1 <=> 1) == 0
    

    成员运算符

    成员运算符in相当于调用contains或isCase方法。

    def list = ['Grace','Rob','Emmy']
    assert ('Emmy' in list)
    

    相等运算符

    ==运算符和Java中的不同。在Groovy中它相当于调用equals方法。如果需要比较引用,使用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)   //比较引用相等
    

    转换运算符

    我们可以使用Java形式的(String) i来转换类型。但是假如类型不匹配的话,就会抛出ClassCastException。而使用as运算符就会避免这种情况。

    Integer x = 123
    String s = x as String 
    

    如果希望自己的类也支持as运算符的话,需要实现asType方法。

    表达式和语句

    声明变量

    Groovy支持以传统方式使用变量类型 变量名的方式声明变量,也可以使用def关键字声明变量。使用def关键字的时候,变量类型由编译器自动推断,无法推断时就是Object类型。

    Groovy可以同时声明多个变量。

    def (a, b, c) = [10, 20, 'foo']
    

    如果左边的变量数比右面的值多,那么剩余的变量就是null。

    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方法。

    class CustomDestruction {
        int a
        int b
        int c
        //解构需要实现getAt方法
        def getAt(int i) {
            switch (i) {
                case 0: a; break
                case 1: b; break
                case 2: c; break
                default: throw new IllegalArgumentException()
            }
        }
        static void main(String[] args) {
            //对象解构
            def obj = new CustomDestruction(a: 3, b: 4, c: 5)
            def (x, y, z) = obj
            println("x=$x,y=$y,z=$z")
        }
    }
    

    条件语句

    Groovy的if语句和Java的类似。不过在Groovy中布尔值的真假不仅看条件比较的结果,还可以以其他情况判断。前面已经介绍过了。switch语句同理,真值判断非常自由。详情可参见Groovy文档 真值判断

    循环语句

    Groovy支持传统的Java的for(int i=0;i<N;i++)for(int i :array)两种形式。另外还支持for in loop形式,支持迭代范围、列表、Map、数组等多种形式的集合。

    // 迭代范围
    def x = 0
    for ( i in 0..9 ) {
        x += i
    }
    assert x == 45
    
    // 迭代列表
    x = 0
    for ( i in [0, 1, 2, 3, 4] ) {
        x += i
    }
    

    while语句的形式和Java相同。

    断言语句

    前面我们看到了很多Groovy断言的例子。Groovy断言语句的功能很强大,以至于文档中写的是强力断言(Power assertion)。

    Groovy断言的形式如下。Groovy断言和Java断言完全不同。Groovy断言是一项语言功能,一直处于开启状态,和JVM的断言功能-ea完全无关。所以它是我们进行单元测试的首选方式。

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

    比如我们要断言1+1=3。结果应该类似这样。越复杂的表达式,断言效果越清晰。有兴趣的同学可以试试。

    Caught: Assertion failed:
    
    assert 1+1 == 3
            |  |
            2  false
    

    面向对象编程

    Groovy的面向对象编程和Java类似,但是提供了一系列功能简化面向对象开发。当然如果你想使用传统的Java语法来声明所有成员也可以,Groovy设计目的之一就是让Java程序员能够以低成本的方式切换到Groovy上。

    • 字段默认是私有的,Groovy会自动实现Getter和Setter方法。
    • 方法和属性默认是公有的。
    • 类不必和文件名相同,
    • 一个文件可以有多个类,如果一个类也没有,该文件就会被看做是脚本。

    构造器

    Groovy的构造器非常灵活,我们可以使用传统的Java方式声明和使用构造器,也可以完全不声明构造器。有时候不声明反而更简单。如果没有声明构造器的话,我们可以在构造对象的时候使用命名参数方式传递参数,这种方式非常方便,因为我们不需要声明所有参数,只要声明所需的参数即可。

    如果希望对构造器进行限制,可以手动声明构造器,这样这种自动构造就不会进行了。

    class Product {
        String name
        double price
    
        String toString() {
            return "Product(name:$name,price:$price)"
        }
    
        static void main(String[] args) {
            def product = new Product(name: 'AMD Ryzen 1700', price: 2499)
            println("隐式构造器:$product")
        }
    }
    
    

    方法

    Groovy方法和Java方法基本相同。不过Groovy方法更方便:支持命名参数和默认参数。另外Groovy方法可以使用def关键字声明,这时候方法返回类型是Object。在Groovy中方法的返回语句可以省略,这时候编译器会使用方法的最后一个语句的值作为返回值。在前面我们还看到了def关键字定义变量,这时候变量的类型需要从代码中推断。

    在使用命名参数的时候需要注意一点,方法参数需要声明为Map类型(不需要详细指定键和值的类型),在调用方法的时候使用命名参数方式传入参数。

    def foo(Map args) { "${args.name}: ${args.age}" }
    //调用方法
    foo(name: 'Marie', age: 1)
    

    另外方法的括号是可选的,我们可以省略括号直接像这样调用方法。

    methodWithDefaultParam '555', 42
    

    字段

    Groovy中字段和Java中的概念类似。不过Groovy更加方便:默认情况下字段是私有的,Groovy自动生成字段的Getter和Setter。如果需要更精细的控制,把它当成Java字段用就行了。不过如果自定义字段的话,Groovy不会自动生成对应的属性了。

    属性

    如果字段上面没有声明访问修饰符(private、public这些),Groovy就会自动生成Gettter和Setter。如果字段是final的,那么只会生成Getter。这就是Groovy方便的属性功能。

    当然Groovy的方便不止于此,我们的所有类似Java访问字段的语法,实际上都会调用字段对应的Getter和Setter。这样显著减少了代码量。如果在类内部的话,.字段语法会直接访问字段,这样做是为了防止无限递归调用属性。

    下面的例子中,第一次调用p.name = 'Marge'如果在类内部,就直接写入字段,如果调用在类外部,就会使用Setter写入。第二次调用使用了方法语法,直接使用Setter写入,所以不管在类内还是类外,写入的值都是"Wonder$name"

    class Person {
        String name
        void name(String name) {
            this.name = "Wonder$name"       
        }
        String wonder() {
            this.name                       
        }
    }
    def p = new Person()
    p.name = 'Marge'                        
    assert p.name == 'Marge'                
    p.name('Marge')                         
    assert p.wonder() == 'WonderMarge' 
    

    特征类

    Groovy和Scala一样,支持特征类(trait)。特征类就好像自带实现的接口。在Java中只能继承一个类和多个接口。在Groovy中,我们可以继承多个特征类。特征类和普通的Groovy类一样,可以包括属性、字段、方法等,特征类也可以是抽象的。

    使用特征类,我们可以在Groovy中实现类似C++的多重继承。另外,特征类支持运行时动态绑定,在某些情况下非常有用。

    trait Readable {
        void read() {
            println("read...")
        }
    }
    
    trait Writable {
        void write(String text) {
            println("write $text")
        }
    }
    
    class Notebook implements Readable, Writable {
        static void main(String[] args) {
            //使用特性类
            def notebook = new Notebook()
            notebook.read()
            notebook.write("something")
        }
    }
    

    闭包

    闭包是Groovy非常重要的一个功能,也是我们介绍的最后一个功能。要了解闭包,最好先知道Java的Lambda表达式、匿名内部类等概念。Groovy闭包和Lambda表达式概念相近,但是功能更强大。

    声明闭包

    闭包的形式如下。

    { [closureParameters -> ] statements }
    

    以下都是合法的Groovy闭包。所有闭包都是groovy.lang.Closure类型的实例。闭包的参数类型是可选的。如果闭包只有单个参数,参数名也是可选的。Groovy会隐式指定it作为参数名。Kotlin语言也是类似的做法,有助于我们先出可读性很好的闭包。

    { item++ }                                          
    
    { -> item++ }                                       
    
    { 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()
    }
    

    调用闭包

    闭包既可以当做方法来调用,也可以显示调用call方法。

    def code = { 123 }
    assert code() == 123
    assert code.call() == 123
    

    调用有参数的闭包也是类似的。

    def isOdd = { int i-> i%2 == 1 }                            
    assert isOdd(3) == true                                     
    assert isOdd.call(2) == false  
    

    使用闭包

    Groovy闭包类似Java的Lambda表达式和匿名内部类,不过使用更方便,能让我们减少不少代码量。闭包还可以作为方法参数传递到其他地方,这让闭包更加灵活。

    static void funWithClosure(Closure closure) {
        closure()
    }
    
    //在其他地方调用该方法
    funWithClosure({ println("Hello yitian") })
    
    //括号还可以省略,更加简练
    funWithClosure { println("Hello yitian") }
    

    关于闭包,还有几个精彩的例子,就是Gradle脚本和Groovy模板引擎,它们都利用了Groovy强大的闭包功能,构建出简练而强大的DSL,让我们用很少的代码就可以实现强大的功能(虽然学起来稍微复杂点)。

    参考资料

    Groovy官方文档 语法

    相关文章

      网友评论

          本文标题:Groovy 知识点总结

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