即学即用Gradle - Groovy中的11个特性

8,366 阅读6分钟

前言

纠结了一个多星期,写一个Gradle教程还是打算先从 Groovy 基础讲起,虽说现在可以直接使用 kotlin,但毕竟大部分还是 Groovy。

因为因为已经有很多 Groovy 教程,所以不打算每个都讲一遍,与 Java 和 Kotlin 相比,罗列了11个不同点,希望你会喜欢~

简介

Groovy 是一个:

  • 基于 Java
  • 具有静态类型和静态编译功能
  • 简洁高效
  • 支持DSL
  • 既能面向对象、又能纯粹用于脚本

动态语言。

如果你是 Java 的使用者,那么恭喜你,你可以无缝接入Groovy,如果你还是一个 Kotlin 的使用者,那么我要再次恭喜你,你基本上可以起飞了,下面开始我们的学习之旅。

1. 分号可以省略

和 Kotlin 一样,Groovy 中的分号是可以省略的:

class Student {
    static void main(String[] args){
        println "Hello World!"
    }
}

Java 要求一行代码结束的时候要求 ; 结尾。

2. 弱类型定义方式

Groovy 和 Java 不太一样,它有两种变量定义方式:

  1. 强类型定义方式
  2. 弱类型定义方式

强类型定义方式 指的是我们声明变量的时候,同时声明变量类型,比如像这样:

private String name

弱类型定义方式 需要使用关键字 def

 Student {
    static void main(String[] args){
        // 弱类型定义方式
        def name1 = "JiuXinDev"
        println name1
        // 强类型定义方式
        String name2 = "Chen88"
        println name2
    }	
}

def 给予了我们在一开始不需要声明变量类型的权利,等到运行时再做判断,这与 Java 中的编译时静态类型是完全不一样的。

我们知道,Kotlin 中的 varval 也不需要声明变量类型,那它是否跟 groovy 中的 def 是否一样呢?

答案是不一样,因为 Kotlin 是静态类型语言,但是 Kotlin 具有类型推断的能力,所以它的类型在编译时就已经确定。

3. 范围

学习过 Kotlin 的同学应该对范围很熟悉了,Groovy 与之对应的类是 Range,我们可以这么使用:

class Student {
    static void main(String[] args){
        def range = new IntRange(0,10);
        println range;
    }	
}

输出

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Groovy 可以使用 .. 操作符表达 Range,所以使用起来会更加方便:

class Student {
    static void main(String[] args){
        def r1 = 0..10;
        println r1;
        def r2 = 10..0;
        println r2;
        def r3 = 'a'..'z';
        println r3;       
    }	
}

输出:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
[a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z]

不仅可以操作数字,还可以操作字符,结合字符串和列表,使用起来更加优雅。

4. 字符串

Groovy 中的字符串可以使用单引号、双引号和三引号三种。

单引号

单引号的使用方式跟 Java 一致:

 Student {
    static void main(String[] args){
        String name = 'JiuXinDev';
        int age = 25;
        println "name: " + name + ", age: " + age;
    }	
}

输出:

name: JiuXinDev, age: 25

双引号

双引号跟单引号相比,多了一个插值的功能,其实跟 Kotlin 中的字符串使用一致:

 Student {
    static void main(String[] args){
        String name = 'JiuXinDev'
        int age = 25
        println "name: $name, age: $age"
    }	
}

输出:

name: JiuXinDev, age: 25

使用方式就是把 ${变量} 插在字符串中。

三引号

三引号的场景不多, 可以帮助我们保持输出格式:

class Student {
    static void main(String[] args){
        String str = '''冲天香阵破长安,
满城尽带黄金甲。
        ''';
        println str;
    }	
}

输出:

冲天香阵破长安,
满城尽带黄金甲。

避免了换行符的使用。

字符串索引

和 Kotlin 一样,Groovy 有一个范围运算符,用 .. 表示,比如要表示 0 到 10,可以用 0..10 表示。代码:

class Student {
    static void main(String[] args){
        String str = "Hello World";
        println str[1];
        println str[0..4];
    }	
}

输出:

e
Hello

除了以上的特点,Groovy 还为字符串提供了其他的一些方法,感兴趣的同学可以去官网看一下。

5. 访问控制

Groovy 和 Java 一样,有三种访问修饰符,publicprivateprotected

在不添加默认修饰符的情况下,Java 的默认访问权限是包访问权限,Groovy 和 Kotlin 一样,默认的访问权限是 public,包括类、方法和成员变量。

6. 相等性判断

Groovy 中的 == 操作符等价于使用 equals 方法,该方法可以复写,地址比较请用 is 方法:

class Name{
    String name;

    static void main(String[] args){
        def n = new Name()
        n.name = "JiuXinDev"
        def b = new Name()
        b.name = "JiuXinDev"
        assert n == b // 通过
        assert n.is(b) // 不通过
    }

    @Override
    boolean equals(Object obj) {
        return obj instanceof Name && name == obj.name
    }
}

其他文章说使用 == 操作可以避免空指针异常,但是我在 Groovy 3.0.6 版本上直接使用 equals 方法也没有抛出异常。

7. Null处理

安全调用运算符 ?.

通过使用安全调用运算符 ?. 把一次 NULL 检查和一次方法调用合并成一个操作 :

class Name{
    static void main(String[] args){
        String s = null
        println s?.length()
    }
}

表达式 s.length() 等价于 if(s != null) s.length(),不过,由于 snull,所以输出的也是 null。当然了,这个调用符也可以访问成员变量。

Elvis 操作符

Elvis 操作符 ?: 是一种特殊的三元操作符,借助它,你可以简化三元表达式:

class Student {
    static void main(String[] args){
        String a = null
        // def result = a != null ? a : "name"
        def resultA = a ?: "name"
        println resultA
        
        String b = "haha"
        def resultB = b.length() > 5 ?: "JiuXinDev"
        println resultB
    }
}

对于一次赋值,可以将 def result = a != null ? a : "name" 简化成 def resultA = a ?: "name"

一切皆可转化为Boolean

任何为 nullVoid的对象,或者等同于 0 或者为 null 的值,都会被解析为 false,反之则为 true

所以一般对 String 判断可以从 str != null && str.length() > 0 可以改为 !str

8. 方法

方法也可以用 def 修饰符。

返回值

当我们声明了返回值类型,但是没有显示的声明 return 语句,默认使用最后一行作为返回语句。

class Student {
    static void main(String[] args){
        println getNum(5);
    }	
    
    static def getNum(int a){
        if(a > 2){
            1;
        }else {
            0;
        }
    }
}

方法参数

当你使用 def 修饰参数的时候,那么你的 def 可以省略:

class Student {
    static void main(String[] args){
        println getNum(5);
    }	
    
    static def getNum(a){
        if(a > 2){
            1;
        }else {
            0;
        }
    }
}

多个参数的时候,我们需要保证参数的名称不能一致。

我们也可以给参数设置默认参数,像这样:

class Student {
    static void main(String[] args){
        println getMax(5);
    }	
    
    static def getMax(a,b=2){
        if(a > b){
            1;
        }else {
            0;
        }
    }
}

我看 w3c 上说,如果使用非默认和默认参数,则必须注意,默认参数应在参数列表的末尾定义。我直接将默认参数定义在头部,非默认参数定义在尾部也没出现什么问题。

不加具体类型的参数可以让代码看着更加简洁,但是对于后期维护的同学来说,可能一点都不友好,因此规定好开发规范还是很有必要的。

省略括号

在顶级表达式中,可以省略括号,比如我们在之前多次使用的 println 方法。

class Student {
    static void main(String[] args){
        println 2;
        println 2*2
        // println doSomeThing 2; 运行失败
        println doSomeThing(2) * doSomeThing(2);
        println doSomeThing(2);
        // 使用闭包
        callSomeThing {
            println "Hello World!";
        };
    }
    
    static int doSomeThing(int value){
        return value + value;
    }
    
    static void callSomeThing(Closure c){
        c.call();
    }
}

println 后面,你去调用 22*2、函数等都是没问题的,但是你如果再使用一个省略括号的表达式,比如 doSomeThing 2 就是不行的,除了普通方法以外,另外一个常见场景就是使用闭包,我会在下文介绍。

9. 列表

Groovy 中的 List 的使用方法基本跟 Java 一致,不过,Groovy 增加了一些自己的Api,比如:

class Student {
    static void main(String[] args){
        def list1 = [2,3,4,6,9,12];
        def list2 = [2,7];
        // plus 两个集合合并,生成一个新的集合
        println list1.plus(list2);
        // minus 第一个集合减去和第二个集合相交的部分
        println list1.minus(list2);
        // 通过索引取值
        println list1[1];
        // 通过范围修饰符取值
        println list1[1..3];
        // 通过 << 往列表中增加新的值
        list1 << 99
        println list1
    }
}

这段代码中涉及了 plusminus、范围和 << 操作符的时候,方法说明我已经写在注释里了,使用风格和 Kotlin 中的列表比较相似。

10. 特征

除了接口和类,Groovy中还有一个新的东西,它叫做特征,你可以理解其为具有默认实现和状态的类,或者具有多继承能力的类,需要使用 implements 关键字,它可以实现接口。

class Student {
    static void main(String[] args){
        def developer = new SeniorDevelop();
        developer.drinkWater();
        developer.name = "小李";
        developer.writeCode();
    }	
}

interface Person{
    void drinkWater();
}

trait Man implements Person{
    void drinkWater(){
        println "一天8杯水";
    }
}

trait Coder{
    String name = "小王";
    
    void writeCode(){
        println "一天200行代码";
    }
}

class SeniorDevelop implements Man,Coder{
    void writeCode(){
        println getName() + "一天可以写500行代码";
    }
}

SeniorDevelop 实现了特征 ManCoder,并且可以复写特征里面的方法和使用特征中的属性,输出:

一天8杯水
小李一天可以写500行代码

11. 闭包

Groovy 中的函数式编程称为闭包。

第一个闭包

闭包是一个匿名代码块:

class Name{
    static void main(String[] args){
        def printHelloWorld = { println "Hello World!" }
        // 1. 直接使用 闭包名()
        printHelloWorld()
        // 2. 使用call方法 闭包名.call()
        printHelloWorld.call()
    }
}

调用方法有两种,一种直接调用,另外一种是使用 call 方法。

加入形参

如果有参数呢?

class Name {
    static void main(String[] args) {
        def greet = { name -> println "Hello, this is $name" }
        greet("QiDian")
        greet.call("JiuXin")
    }
}

在有参数的情况,我们需要先声明形参,然后在调用处的括号里面加上实参即可。

引入变量

在闭包中,还可以引入变量,包括方法中的局部变量和类中的变量:

class Name {
    static void main(String[] args) {
        def company = "YueWen"
        def greet = { name -> println "Hello, this is $name, it is from $company" }
        greet("QiDian")
    }
}

输出:

Hello, this is QiDian, it is from YueWen

用作参数

闭包可以被当作参数传递:

class Name {
    static void main(String[] args) {
        def clo = { String name, int level ->
            int salary = level * 10000
            println "$name salary is $salary yuan!"
        }
        calculateSalary(clo)
    }

    static void calculateSalary(Closure closure) {
        closure.call("Chen", 3)
    }
}

前提是你知道闭包中有哪些参数。

with方法

Groovy 中的这个 with 方法跟 Kotlin 中的 apply 方法类似,它使用了闭包:

class Name {
    String firstName;
    String secondName

    static void main(String[] args) {
        def name = new Name()
        name.with {
            firstName = "乘"
            secondName = "风"
            println "$firstName $secondName"
        }
    }
}

with 方法中的这个闭包,我们可以直接使用 firstName 这个成员变量,也可以使用内部中的 public 方法。

委托

上述 with 方法之所以能够调用对象中的成员变量和方法,是因为它改变了委托策略。

闭包中的委托有三个重要的概念,它们分别是 this \ owner \ delegate。它们的区别是:

  • this:闭包定义处外部的类或者这个类对象。
  • owner:闭包定义处外部的类或者类对象或者外层的闭包对象
  • delegate:可以是任何对象,默认 owner

我们以一个简单的demo为例:

class Name {
    static void main(String[] args) {
        def staticClo = {
            println "staticMethod this: " + this.toString()
            println "staticMethod owner: " + owner.toString()
            println "staticMethod delegate: " + delegate.toString()
        }
        staticClo.call()

        def name = new Name()
        name.with {}
        name.doSomeThing()
    }

    def clo = {
        println "class this: " + this.toString()
        println "class owner: " + owner.toString()
        println "class delegate: " + delegate.toString()

        def innerClo = {
            println "innerClo this: " + this.toString()
            println "innerClo owner: " + owner.toString()
            println "innerClo delegate: " + delegate.toString()
        }
        innerClo.call()
    }

    void doSomeThing(){
        clo.call()
    }
}

输出:

staticMethod this: class Name
staticMethod owner: class Name
staticMethod delegate: class Name
class this: Name@6ef888f6
class owner: Name@6ef888f6
class delegate: Name@6ef888f6
innerClo this: Name@6ef888f6
innerClo owner: Name$_closure1@5223e5ee
innerClo delegate: Name$_closure1@5223e5ee

在静态方法中,闭包中的 thisownerdelegate 都一样,指的是 Name 这个 class。在成员变量中,thisownerdelegate 也一样,指的是当前的 Name 对象。最后我们在闭包中又定义了一个闭包,this 指的是 Name 对象,而 ownerdelegate 指的是外部闭包对象。

虽然 delegateowner 默认是一致的,但是我们可以更改 delegate

class Person {
    String name
    String level

    def clo = {
        println "$name - $level"
    }

    static void main(String[] args) {
        def p1 = new Person()
        p1.name = "XiaoWang"
        p1.level = "1"

        def p2 = new Person()
        p2.clo.delegate = p1
        p2.clo.resolveStrategy = Closure.DELEGATE_FIRST
        p2.clo.call()
    }
}

输出:

XiaoWang - 1

我们没有给 p2namelevel 设置任何字符串,只是因为我们给闭包 clo 更改了 delegate,仅仅更改了 delegate 不能达到效果,还需要更改委托策略,委托策略有:

  1. OWNER_FIRST:默认策略,先调用 owner 中的方法和属性,没有,调用 delegate
  2. DELEGATE_FIRST:先调用 delegate 中的方法和属性,没有,再调用 owner
  3. OWNER_ONLY:仅获取 owner 中的方法和属性。
  4. DELEGATE_ONLY:仅获取 delegate 中的方法和属性。
  5. TO_SELF:需要自定义 Closure 子类,自定义解析策略。

我们将 OWNER_FIRST 修改为 DELEGATE_FIRST,这样,p2 中闭包 clodelegate 指向了 p1,就可以优先使用 p1 的成员变量 namelevel 了。

总结

整体而言,Groovy 的语言像是 Java 和 Kotlin 的结合体,学习成本也不算特别大。

精彩内容

如果觉得本文不错,「点赞」是对作者最大的鼓励~

技术不止,文章有料,关注公众号 九心说,每周一篇高质好文,和九心在大厂路上肩并肩。

引用文章:

《语法风格指南》
《w3c Groovy教程》