Groovy 快速入门

2022-05-05 19:57:36 浏览数 (1)

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

基本内容

注释

单行注释,以//开头。

多行注释,/* */

GroovyDoc注释,和JavaDoc注释类似。

代码语言:javascript复制
    /**
     * Creates a greeting method for a certain person.
     *
     * @param otherPerson the person to greet
     * @return a greeting message
     */

Shebang注释,和Linux其他注释类似,用于指定脚本解释器的位置。

代码语言:javascript复制
#!/usr/bin/env groovy

标识符

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

字符串

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

代码语言:javascript复制
def str="1234"
def str2='1234'

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

代码语言:javascript复制
def multiline="""line1
line2
line3
"""

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

代码语言:javascript复制
def name = 'Guillaume' // a plain string
def greeting = "Hello ${name}"

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

代码语言:javascript复制
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方法传入单个字符的话,可以使用下面的方法进行转换。

代码语言:javascript复制
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中的变量很相似。

代码语言:javascript复制
int   i = 4

BigInteger bi =  6

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

代码语言:javascript复制
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运算符自己选择合适的列表底层类型。

代码语言:javascript复制
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差不多。我们使用[索引]引用和修改列表元素。如果索引是负的,则从后往前计数。要在列表末尾添加元素,可以使用左移运算符<<。如果在方括号中指定了多个索引,会返回由这些索引对应元素组成的新列表。使用两个点加首位索引..可以选择一个子列表。

代码语言:javascript复制
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']    

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

代码语言:javascript复制
def multi = [[0, 1], [2, 3]]     
assert multi[1][0] == 2   

数组

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

代码语言:javascript复制
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的实际类型。

代码语言:javascript复制
def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF']   

assert colors['red'] == '#FF0000'    
assert colors.green  == '#00FF00' 

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

代码语言:javascript复制
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。或者更直接,对象非空是使用对象本身,为空时给另一个值,常用于给定某个可空变量的默认值。

代码语言:javascript复制
displayName = user.name ? user.name : 'Anonymous'   
displayName = user.name ?: 'Anonymous'   

安全导航运算符

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

代码语言:javascript复制
def person = Person.find { it.id == 123 }    
def name = person?.name                      
assert name == null   

字段访问运算符

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

代码语言:javascript复制
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语言非常灵活。

代码语言:javascript复制
def str = 'example of method reference'            
def fun = str.&toUpperCase                         
def upper = fun()                                  
assert upper == str.toUpperCase()   

展开运算符

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

代码语言:javascript复制
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值,不会抛出空指针异常,而是返回空值。

代码语言:javascript复制
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使用。

代码语言:javascript复制
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方法。

代码语言:javascript复制
assert (1 <=> 1) == 0

成员运算符

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

代码语言:javascript复制
def list = ['Grace','Rob','Emmy']
assert ('Emmy' in list)

相等运算符

==运算符和Java中的不同。在Groovy中它相当于调用equals方法。如果需要比较引用,使用is

代码语言:javascript复制
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运算符就会避免这种情况。

代码语言:javascript复制
Integer x = 123
String s = x as String 

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

表达式和语句

声明变量

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

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

代码语言:javascript复制
def (a, b, c) = [10, 20, 'foo']

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

代码语言:javascript复制
def (a, b, c) = [1, 2]
assert a == 1 && b == 2 && c == null

如果等号右面比左面多,那么多余的值会被忽略。

代码语言:javascript复制
def (a, b) = [1, 2, 3]
assert a == 1 && b == 2

自定义对象也可以用多重赋值进行对象解构。该对象必须有getAt方法。

代码语言:javascript复制
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、数组等多种形式的集合。

代码语言:javascript复制
// 迭代范围
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完全无关。所以它是我们进行单元测试的首选方式。

代码语言:javascript复制
assert [left expression] == [right expression] : (optional message)

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

代码语言:javascript复制
Caught: Assertion failed:

assert 1 1 == 3
        |  |
        2  false

面向对象编程

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

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

构造器

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

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

代码语言:javascript复制
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类型(不需要详细指定键和值的类型),在调用方法的时候使用命名参数方式传入参数。

代码语言:javascript复制
def foo(Map args) { "${args.name}: ${args.age}" }
//调用方法
foo(name: 'Marie', age: 1)

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

代码语言:javascript复制
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"

代码语言:javascript复制
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 的多重继承。另外,特征类支持运行时动态绑定,在某些情况下非常有用。

代码语言:javascript复制
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表达式概念相近,但是功能更强大。

声明闭包

闭包的形式如下。

代码语言:javascript复制
{ [closureParameters -> ] statements }

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

代码语言:javascript复制
{ 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方法。

代码语言:javascript复制
def code = { 123 }
assert code() == 123
assert code.call() == 123

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

代码语言:javascript复制
def isOdd = { int i-> i%2 == 1 }                            
assert isOdd(3) == true                                     
assert isOdd.call(2) == false  

使用闭包

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

代码语言:javascript复制
static void funWithClosure(Closure closure) {
    closure()
}

//在其他地方调用该方法
funWithClosure({ println("Hello yitian") })

//括号还可以省略,更加简练
funWithClosure { println("Hello yitian") }

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

参考资料

Groovy官方文档 语法

0 人点赞