美文网首页开发工具
即学即用Gradle - Groovy中的11个特性

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

作者: 九心_ | 来源:发表于2021-04-06 10:44 被阅读0次

    前言

    纠结了一个多星期,写一个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 的结合体,学习成本也不算特别大。

    引用

    相关文章

      网友评论

        本文标题:即学即用Gradle - Groovy中的11个特性

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