Groovy 语言学习基础

1,761 阅读10分钟
原文链接: liweijieok.github.io

个人Groovy目前学习所用主要是用于编写gradle插件。groovy语言是兼容Java语言的,我们可以使用写java的方式写Groovy,当然也可以使用Groovy本身的一些语法去写也许。Groovy是没有类型的java。

来源Groovy入门教程

Groovy安装

下载GDKGDK,配置安装环境,也就是环境变量,跟java的类型(AS直接运行貌似可以不用),然后在Android Studio中开发Groovy,第一次运行的时候需要配置lib的路径,根据提示选择到groovy的安装目录下面的lib目录设置进去。这样子就可以了。

Groovy基本规则

  • 作为动态语言,Groovy中所有的变量都是对象,在声明一个变量时,不强制声明类型,只是需要在变量前使用def关键字。
  • 属性默认都是private的,默认提供get和set方法,类默认都是public的。
  • 语句结束的时候不需要”;”表示语句结束
  • 文件名以.groovy结尾
  • 注释,他的注释与java一样,支持

    只能在第一行的注释:#!
    单行注释://
    多行注释:/**/
    文档注释,也就是编译到doc里面的:/** */
    
  • 变量命名
    以字母,美元符号$,或者是下划线_开始,不能以字符串开始

  • Groovy的保留字

asassertbreakcasecatchclassconstcontinuedefdefaultdoelseenumextendsfalsefinallyforgotoifimplementsimportininstanceofinterfacenewnullpackagereturnsuperswitchthisthrowthrowstraittrue

try

  • Groovy的自动导入的包包括:
    groovy.lang.*
    groovy.util.*
    java.lang.*
    java.util.*
    java.net.*
    java.io.*
    java.math.BigInteger
    java.math.BigDecimal
    

使用到这些包的时候我们就不用去import了

Groovy语法基础

HelloGroovy程序:

我们首先在AS里面新建一个JAVA 程序或者是建立一个Android lib或者是app module,随便都行。然后新建一个名字随便的.groovy文件。比如mygroovy.groovy文件,文件的位置是位于src/main/java目录下面。然后按照JAVA的写法输出HelloGroovy是:在mygroovy.groovy新建一个类,类的名字是随便,例子如下:

package com.example
public class HelloGroovy{
    public static void main(String[] args) {
        System.out.println("Hello Groovy")
 
    }
}

然后对着代码右键,选择run,然后假如是第一次需要配置Groovy的jar进来。就选择你下载的Groov目录下面的lib目录配置进去,再次运行,就可以输出:

Hello Groovy

这里的输出还是调用java的输出流实现的。其他写法:不需要类,不需要main方法,直接是:

package com.example
System.out.println("Hello Groovy")

右键,run,还是输出Hello Groovy。这里的输出还是使用JAVA语言的输出流实现的。最简单的写法,直接:

package com.example
print("Hello Groovy")
print "Hello Groovy"

同样会都输出Hello Groovy,这里就是使用Groovy语言实现的。

定义变量

我们可以使用java的强语言类型,使用各种不同的类型去定义变量,在这里不分享。Groovy还可以通过关键字def定义变量,他会根据上下文去推断这个变量的类型,需要强调一点,Groovy里面def声明一切都是对象。比如:

package com.example
def a=10
def b=10L
def c="Hello Groovy"
def d=1.0
def e=1.0f
println(a.class)
println(b.class)
println(c.class)
println(d.class)
println(e.class)

输出:

class java.lang.Integer
class java.lang.Long
class java.lang.String
class java.math.BigDecimal
class java.lang.Float

同时,在给一个变量赋值之后,也就是给定了类型之后,该变量还是可以指向别的类型的,比如:

package com.example
def a=10;
println(a.class)
a = "Hello Groovy"
println(a.class)

输出就是:

a=10;
println(a.class)
a = "Hello Groovy"
println(a.class)

但是,你以为就一定是需要def关键字吗,个人在AS里面测试运行的时候发现,我们可以把关键字def都省略掉,直接使用一个变量名字就可以,这就是动态语言的强大。变量堆指向的类型不关心,只要你给他什么类型,他就可以是什么类型。上面的例子是可以写成:

package com.example
a=10;
println(a.class)
a = "Hello Groovy"
println(a.class)

这样子也是ok的。

类型兼容问题

看下面一个例子:

package com.example
 
char c = 'a'
println c.class
c='b'
println c.class
c2='a'
c3="a"
println c2.class
println c3.class

首先我们使用java的char关键字声明一个char 类型的变量c,然后打印类型,再次改变内容,再次打印类型,然后声明c2和c3,打印类型:输出结果是:

class java.lang.Character
class java.lang.Character
class java.lang.String
class java.lang.String

可以看到,只要是使用java声明了类型,而后在后面改变了c的值,他的类型是没有改变的,但是直接声明c2和c3,无论使用’’还是””,类型都是String。所以我们得知,在Groovy直接声明变量中是只能变为String,而不能变为Character。除非你是强制类型装换或者是使用as char,比如:

package com.example
 
c = (char) "c"
c2 = "c" as char

这样子就可以装换为Character。那么假如我们使c指向一个字符串会怎么样子呢?答案是会报错,例如:

package com.example
 
char c = 'a'
println(c.class)
c="aa"
println(c.class)

就会报

Caught: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'aa' with class 'java.lang.String' to class 'char'

但是假如你是把c指向int,float,double,只要是没有超出char的范围,或者是只有一个字符的字符串,比如“A”,而且他是可以使用Character.valueOf()方法转化的,那么它也会被赋值成功。
也就是说,使用java声明的变量,他会保持类型而不能够动态指向不同类型,假如是指向了其他类型,对于基本类型而言,能够使用Type.valueOf()方法转化的就不会出错。

as 保留字
他的函数是a.asType(b),用于指定类型,因为在Groovy中,是没有类型的

Groovy的字符串GString

  • 插值占位符$
    首先我们先看一个例子,理解使用单引号’’和使用双引号””的字符串的区别:
    package com.example
     
    str = "hello"
    str1 = 'hi${str}'
    str2 = "hi${str}"
    str3 = "${str}hi"
    println(str1)
    println(str2)
    println(str3)
    a = 1;
    b = 2;

输出:

hi$str
hihello
hellohi
sum = 3
sum2 = 3

可以看到使用单引号插值占位符不起效。使用插值占位符我们可以替换或者是填补空缺。使用的方式就是

${var}

当$位于语句末端的时候,{}可以省略,其他的一般不要省略,比如上面的str的就可以省略,但是str3的省略就会出错。我们可以用它来插入变量。有点类似JAVA的String的Format。同时,占位符支持符号内的运算,比如:

sum="sum = ${a + b}"
sum2 ="sum2 = ${1 + 2}"
println(sum)
println(sum2)

输出:

sum = 3
sum2 = 3

占位符中调用方法

a = 1;
sum="sum = ${a.hashCode()+getInt() }"
sum2 ="sum2 = ${getInt()}"
println(sum)
println(sum2)
private int getInt() {
    return 3;
}

  1. 三个单引号开始三个单引号结束,比如:
    package com.example
    str='''hello Groovy'''
    println(str)
    

就可以输出hello Groovy。三重引用字符不支持插值占位符的操作,比如:

str = "Groovy"
str1='''hello${str}'''

str1的结果是hello${str},不会是hello Groovy。三重引用字符支持多行字符串,不像简单的用单引号或者是双引号字符串,他们是不支持多行字符串的,比如:

str1='''hello
    Groovy'''
println(str1)
println(str1.contains("\n"))

打印输出的就是:

hello
    Groovy
true

换行符会被替换成”\n”。其他的留白会照常输出。使用”\”表示不转义,它本身是支持转义的,比如:

tr1='''\
Hello\tGroovy'''
println(str1)
println(str1.startsWith("\n"))

输出结果:

Hello    Groovy
false

可以看到换行符没有使用转义,也就没有输出一个换行了。使用\t就是一个tab键距离。

  1. 三个双引号开始三个双引号结束,比如:
    c = "hello"
    str="\
    \n ${c}+\tGroovy"
    println(str)

输出:


hello+    Groovy

可以看到他跟单引号的区别是可以使用占位符。其他的类似。

  • 斜线字符串
    使用/ string/表示,他经常用于正则匹配,跟””字符串类型,比如:
    s="Groovy"
    str=/Hello ${s}/ as String
    str1 = "Hello Groovy" as String
    println(str.equals(str1))
    

输出就是true。同样支持多行和占位符。

Groovy的运算符

Groovy支持Java所有的运算符,但是它本身还支持另外一些运算符

  • 安全占位符
    他的作用是避免空指针,使用?,例如
    String str;
    str2 =str?.hashCode()
    println(str2)

这里是照常输出不会出现空指针,使用?的意思就是它不为空的时候调用对应的属性的方法。使用“.”调用方法或者改属性对应的属性。

  • 次方运算
    使用”num**num”表示,就是求某一个数的多少次方,比如:
    2**3
    

结果就是8,就是2的三次方的意思

  • 简化的三目运算符,比如:
    str = "Hello Groovy" as Strin
    //常规写法
    str2 = str == null ? "Hello Java" : str
    //Groovy写法
    str3 = str?str:"Hello Java"
    // 简化写法
    str4=str?:"Hello Java"
    

其中第一种写法是比较明了的,第二种写法的意思是:当str不为空的时候,调用前者,为空的时候,调用后者,第三种写法的意思是,当str不为空的时候,使用自己,为空的时候使用后者。

还有一部分的运算符,可以参考Operators

Groovy 导入别的类

语法是跟java一样,使用import关键字,比如:

import groovy.xml.MarkupBuilder
import static Boolean.FALSE // 静态导入,可以直接使用它的一些静态变量和方法而不用使用类.方法调用。
import static java.lang.Boolean.FALSE as f// 取别名,可以想下面那样子去使用FALSE。
f a = false

循环

switch/case结构

例子:

def x =1.2
def result = ""
switch ( x ) {
    case "foo":
        result = "found foo"
// lets fall through
    case "bar":
        result += "bar"
        break
    case [4, 5, 6, 'inList']:
        result = "list"
        break
    case 12..30:
        result = "range"
        break
    case Integer:
        result = "integer"
        break
    case Number.class:
        result = "number"
        break
    case ~/fo*/: // toString() representation of x matches the pattern?
        result = "foo regex"
        break
    case { it < 0 }: // or { x < 0 }
        result = "negative"
        break
    default:
        result = "default"
}
println(result

可以看到,在switch case结构体中,他是支持很多种类型的匹配的,可以是正则匹配,可以是类匹配,可以是字符串,数字匹配,也可以是表达式的匹配,按照从上到下的方式进行匹配,这里的输出是Number。

for循环

支持经典的for循环,例子这里也就不写了。还有别的循环。

  • 范围循环,比如:
    int sum;
    for (i in 1..9) {
        sum+=i
    }
    println(sum)

注意的是这里的i是没有指定类型的,直接是i in num,就是i在某个范围内循环,输出的是45.in关键字还可以用于执行变量i为数组,list,map等类型进行循环,比如:

arr = [1,2,3,4] as int[]
sum = 0 as int
for (i in arr) {
    sum+=i;
}
println(sum)

就是输出10

  • 使用闭包循环,闭包的概念在下面,其实我们可以使用一个闭包来实现循环
    在Integer类,Groovy提供了一些方便循环的闭包,比如upto,times,step,例如:
    //从2到5循环,包括3
    2.upto(5, { print(it) })
    println()
    //从0开始循环2次,每次加1
    2.times { print(it) }
    println()
    //从2开始,每次加2,输出小于10的值
    2.step(10, 2, {print(it)})

输出:

2345
01
2468

数据结构

List

list = [1, "2", false];
println(list.getClass())
list.add(3)
list.add(0, "4")
list.addAll([5, 6, 7,1])
println(list)
list.remove(false)
println(list)
println(list.indexOf(1))

输出:

class java.util.ArrayList
[4, 1, 2, false, 3, 5, 6, 7, 1]
[4, 1, 2, 3, 5, 6, 7, 1]
1

支持增删查改,默认的类型是ArrayList,在初始化的时候可以进行赋值。可以通过as操作符强转。比如

	
list = ["A" ,"B","C"] as LinkedList

Groovy还支持多维的List,比如:

list = [[1, 2], [3, 4]];
sum = 0
for (i in list) {
    for (j in i) {
       sum+=j
    }
}
println(sum)

就会输出10,有点类型多维数组。

Map

map = [A: 1, B: 2, C: 3] as Map
for (i in map) {
    println("Key="+i.getKey()+",value="+i.getValue())
}
map.put("D", 4)
map.remove("A")
println(map)
i = map.get("C")
println(i)

输出是:

Key=A,value=1
Key=B,value=2
Key=C,value=3
[B:2, C:3, D:4]
3

map同样支持初始化的时候进行赋值,他的赋值是,[key:value…],支持Java的map的所以操作。当我们没有为map指定类型的时候,他会根据上下文去推断map 的类型。

数组

数组的定义跟JAVA的类似,比如

arr = [1, 2, 3, 4, 5] as int[]
String[]  arr2 = ["A", "B", "C"]
sum = 0;
for (i in arr) {
    sum+=i;
}
String str="";
for (i in arr2) {
    str+=i
}
println(sum)
println(str)

输出了:

15
[A, B, C]

这里需要注意的是定义数组的时候不要跟定义List混淆,arr = [1, 2, 3, 4, 5]是定义为一个List。arr[]=[1,2,3,4,5]是一种错误的定义方法。

Groovy封装定义

定义类

首先基本的类型有:integral 整形,byte 8字节, short 16字节, int 32字节,long 64字节浮点类型:float 32字节,double 64字节布尔类型 boolean 只有true或者falsechar 类型,16字节,可以作为一个numeric,代表UTF-16字符集。

他们都有对应的包装类,就是JAVA对应的那些包装类。

  1. 该类是自动默认是public的,为属性默认提供get和set方法
  2. 他们的属性和方法没有访问修饰符修饰的时候是public的。
  3. 一个Groovy文件可以包含多个类。
    例子:我们在bean包下定义一个person类如下:
    package com.example.bean
    class Person{
        String name;
        Integer age;
     
        @Override
        String toString() {
            return "Name=${name},age=${age}"
        }

然后在上一级的包中去测试:

package com.example
 
import com.example.bean.Person
def person = new Person()
person.age = 30;
person.name="Hello"
println(person)

发现正常打印结果。同时我们可以通过他默认提供的get/set方法设置属性值。但是有一点我们需要注意,假如有时候我们自己定义了get和set方法,这里面的get和set方法跟默认生成的不一样。假如是直接调用get好set的时候就会调用我们自己生成的,但是假如我们需要调用系统生成的时候,就可以使用域访问符”.@”了,例子如下:

// Person的getName()方法
   public String getName() {
        return "java"
    }
//使用比较
Person p1 = new Person(name: "Groovy", age: 30)
println(p1.getName())
println(p1.@name)

这样子就可以使用系统为我们生成的get/set方法了。
关于内部类,抽象类,接口,接口继承,这些是跟JAVA一样的。这里不再写例子了。

  • 构造方法:
    我们为Person类添加一个构造方法:
    ublic Person(name, age) {
            this.name = name;
            this.age = age;
        }

然后在使用它,可以跟JAVA一样子使用,也可以是下面那样子使用,比如:

// java使用
person = new Person("Hello",12)
println(person)
 //Groovy多出的方式
Person p = ["Groovy",20]
println(p)

参数列表需要跟构造参数的参数列表相对应。还有另外一种更加随性的方式,如下:

Person p1 = new Person(name: "java", age: 30)
Person p2 = new Person(age: 40,name:"PHP")
Person p3 = new Person(name: "hei")
Person p4 = new Person(age:10)

使用这种构造器的时候,我们的类需要没有构造器或者是构造器是空构造器的时候就可以使用。所以,我们一般使用Groovy的时候,假如没有特殊的要求,尽量可以不提供有参数的构造器,以为Groovy会提供足够的构造器给我们使用。

定义方法

例子如下:

def someMethod() { 'method called' }                          
String anotherMethod() { 'another method called' }            
def thirdMethod(param1) { "$param1 passed" }                  
static String fourthMethod(String param1) { "$param1 passed" }

1.不用声明返回类型,而,输入参数可以不指定类型。这是Groovy比较特别的。例如我们在上面的Person类添加一下方法如下:

def getJob() {
        return "Groovy devr"
    }
 
    def setJob(job) {
       return "Good ${job}"
    }

由于没有设置限定修饰符,所以都是public的。调用:

import com.example.bean.Person
Person p1 = new Person(name: "java", age: 30)
println(p1.getJob())
println(p1.setJob("HeiHeiHei"))

这样子就可以输出:

Groovy devr
Good HeiHeiHei

定义属性

一般定义属性跟java类型,可以有修饰符,没有修饰符则是public的。

闭包

终于学习到我们的重点了,闭包。

  • 首先,我们看看闭包是怎么样子的,我们定义一个接口,如下:
    package com.example.closure
    public interface MyClosure{
        void accept(String A)
    }
    

然后使用它:

MyClosure p =  { println(it.contains ('G') )}
p.accept("Groovy")

结果会输出true。这就是一个闭包。闭包的定义;

{ [closureParameters -> ] statements }

说明:

  1. [closureParameters -> ]这部分是可以省略的。statements 可以是0或者是多个语句。
  2. 闭包参数类似于方法参数
  3. 当指定参数之后->是必须的。作用是用来分开闭包参数和语句,语句的个数可以有0个或者多个。
  4. 当语句有多条的时候,可以使用{}包裹起来。
    例子:
    // 简单的闭包
    { 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()
    }

以上例子是来自Groovy官网的例子。

  • 闭包作为对象
    他的类型是Closure,例子如下:
    def listener = { e -> println "Clicked on $e.source" }     
    println listener instanceof Closure
    Closure callback = { println 'Done!' }                     
    Closure isTextFile = {
        File it -> it.name.endsWith('.txt')                    
    }

输出的是:

true

  1. 我们可以把Closure作为一个变量,他指向的实例是一个Closure的子类或本身
  2. 当我们没有使用def关键字命名一个闭包的时候,可以指定他的类型是Closure
  3. 我们可以为Closure引用设置为泛型
  1. 调用闭包对象,比如上面直接是callback()
  2. 调用闭包的call()方法。
  3. 没有指定返回的时候,最后一条语句执行之后的值就是返回类型以及返回值。
    例子如下:
    def code= { 123 }
    println(code() == 123)
    println(code.call()==123)
    

这里定义了一个闭包code,直接调用code()或者是直接调用code.call()方法。当闭包有参数的时候,比如:

def code={s->"Hello ${s}"}
println(code("Groovy"))
println(code.call("Java"))

我们会在闭包调用或者call调用中传入参数,这样子就可以了。

  1. 闭包参数是类型可选的,也就可以不声明类型
  2. 需要一个名称
  3. 可以有一个默认值
    例子如下:
    // 只有一个参数,没有指定类型,返回值是String
    def closureWithOneArg = { str -> str.toUpperCase() }
    assert closureWithOneArg('groovy') == 'GROOVY'
    //指定类型,只有一个参数,返回值是String
    def closureWithOneArgAndExplicitType = { String str -> str.toUpperCase() }
    assert closureWithOneArgAndExplicitType('groovy') == 'GROOVY'
     //多个参数,返回值是int或者是String或者是其他可以执行+运算符的对象
    def closureWithTwoArgs = { a,b -> a+b }
    assert closureWithTwoArgs(1,2) == 3
     //指定类型,返回值是int
    def closureWithTwoArgsAndExplicitTypes = { int a, int b -> a+b }
    assert closureWithTwoArgsAndExplicitTypes(1,2) == 3
     //指定其中一个类型,a的类型需要能够与b进行+运算符操作
    def closureWithTwoArgsAndOptionalTypes = { a, int b -> a+b }
    assert closureWithTwoArgsAndOptionalTypes(1,2) == 3
     //指定一个默认值b为2
    def closureWithTwoArgAndDefaultValue = { int a, int b=2 -> a+b }
    assert closureWithTwoArgAndDefaultValue(1) == 3
    
  • 隐式参数
    当我们没有显示的定义一个参数列表,也没有使用->,闭包中是会包含一个名字为it的变量。假如希望我们的闭包不需要任何的参数,则使用一个空参数加上->例子如下:
    // 默认有一个参数
    def code={"Hello ${it}"}
    println(code.call("Groovy"))
    //跟上面的作用一样
    code={it->"Hello ${it}"}
    println(code.call("PHP"))
    //没有任何的参数
    code= { -> "Hello Java" }
    println(code.call())

输出:

Hello Groovy
Hello PHP
Hello Java

  • 可变参数
    Groovy允许我们在最后一个闭包参数使用可变参数,例如:
    def code={a,String... arg->
        println(a)
        println(arg[0])
        println(arg[1])
    }
    code.call(1, "A", "B")

就会输出1,A,B

  • 闭包作为方法参数
    当一个闭包引用作为另外一个闭包或者是方法的最后一个参数的时候,我们可以安装常规写法,也可以将闭包提取出{}之外写,例如:
    def code= { a, b -> println(a + b) }
    def methodClosure(a,b, Closure c){
        c.call(a,b)
    }
    //第一种调用
    methodClosure(1, 2 ,code)
    // 第二种调用
    methodClosure(1, 2 ,{ a, b -> println(a + b) })
    // 第三种调用
    methodClosure(1,2) {
        a, b ->
        println(a + b)
    }

输出:

2345
3
3
3

Groovy 学习推荐

Documentation
Groovy脚本基础全攻略
Groovy入门教程