美文网首页Android Gradle
Android Gradle(一)——Groovy基础

Android Gradle(一)——Groovy基础

作者: 蓝十一丶 | 来源:发表于2018-07-30 19:48 被阅读50次

    一、Groovy概述

    Groovy是基于JVM的一种动态语言,它结合了Python、Ruby和Smalltalk的特性,同时能与Java代码很好的结合,用于扩展现在的代码,具有以下特点:

    • 具有动态语言的特点,如动态类型转换、闭包和元编程
    • 面向对象编程,同时也可以作为脚本语言
    • 直接编译成Java字节码,在任何使用Java的地方都可以使用Groovy
    • Groovy完全兼容Java语言,无缝集成所有Java已有的库和对象
    • 支持函数式编程,无需main函数
    • 支持DSL,gradle自动化构建系统就是用Groovy语言实现的

    二、Groovy语法

    Groovy完全兼容Java,所以在Groovy文件中可以写任何的Java代码来进行编程,这里主要是讲Groovy不同于Java的语法特性(在Groovy中所有的代码都不必要用;结尾):

    1.变量

    Groovy具有动态类型转换的特点,所以类型对于变量,属性,方法,闭包参数以及函数的返回类型都是可有可无的,在给变量赋值时才确定它的类型,并且在需要时,很多类型之间的转换都可以自动发生。这里要注意的是Groovy可以直接编译成class字节码,JVM是无法判断字节码是Java生成的还是Groovy生成的。除了基本类型外,Groovy中的变量类型都是对象
    下面是一些常见变量的声明的例子

    字符串

    def str1 = '单引号' //Groovy中的字符串可以用单引号来表示
    def str2 = "双引号" //也可以用双引号表示
    

    Groovy中的单引号声明的字符串中不具有运算能力,即不能包含运算表达式,而双引号可以通过${str1}的方式来进行运算

    println '单引号的变量运算:${str1}'
    println "双引号的变量运算:${str2}" //只有双引号才能运算
    println "运算表达式结果为:${str1+str2}" //${}中可以进行运算
    

    输出为:

    单引号的变量运算:${str1}
    双引号的变量运算:双引号
    运算表达式结果为:单引号双引号
    

    集合

    Groovy兼容了Java中的集合,并进行了扩展,如Groovy中的集合中的元素允许使用不同的类型。注意,在Groovy中定义的集合是Java中对应类的对象,可以使用任何Java库提供的方法,如size(),add(E e)等方法。这里主要讲常见的List和Map

    List

    Groovy中的List无需实现List接口,声明如下:

    def numList = [1,2,3,4,5,6]
    def mixList = ['1','2',"start",4,5,6] //Groovy可以自动进行动态类型转换
    

    默认的List是一个ArrayList类型,比如我们可以通过如下方式查看它的类型:

    println numList.getClass().name //输出为:java.util.ArrayList
    

    Groovy访问List的方式与Java不同,无需通过get方法,而是直接使用下标来进行访问的:

    println mixList //输出整个List,为:[1, 2, start, 4, 5, 6]
    println numList[0] //访问第一个元素
    println numList[1] //访问第二个元素
    println numList[-1] //访问倒数第一个元素
    println numList[-2] //访问倒数第二个元素
    println numList[1..3] //访问第二个到第四个元素,输出为:[2, 3, 4]
    println numList[1..3].getClass().name //java.util.ArrayList
    

    Groovy中List还提供了each方法传入闭包来进行迭代操作

    numList.each {
        println it
    }
    

    输出为:

    1
    2
    3
    4
    5
    6
    

    Map

    Map的用法类似于List,区别只是Map是通过键值对的方式来声明的:

    def map = ['name':'Mike', 'age':20]
    println map.getClass().name //java.util.LinkedHashMap
    

    Groovy中的Map默认是LinkedHashMap类型,访问可以通过map[key]或者map.key来进行访问:

    println map['name'] //输出为:Mike
    println map.age //输出为:20
    

    Map也有与List类似的迭代操作:

    map.each {
        println "Key:${it.key}, Value:${it.value}"
    }
    

    输出为:

    Key:name, Value:Mike
    Key:age, Value:20
    

    2.函数

    函数与方法的区别在于函数式独立于类外的,而方法是类中的方法,是一种特殊的函数,两者基本语法相同,方法多了一些类的特性和访问权限而已
    Groovy中的函数与Java中的不同点如下:

    • 可以使用def来定义,定义形参时可以不用显示定义类型,Groovy会自动进行动态类型转换
    • 参数外的括号可以省略,函数名和参数列表之间用空格隔开,无参函数必须带括号
    • 函数嵌套在同一行调用时,只有第一个函数可以省略括号
    • 无需return语句,Groovy会自动将函数中真正执行的最后一句代码作为其返回值,如果使用void修饰函数,函数会返回null(比如定义变量或者直接给出常量,就是有返回值,调用无返回值的函数,如println,就是无返回值)

    例1:

    fun1(10, 11) //输出为:21
    fun1 8,9 //输出为:17
    def fun1(int a, int b) {
        println a+b
    }
    

    例2:

    println fun1(10, 11) //输出为:21
    //println fun1 8,9 这是错误的写法,会报错
    println fun1('abc','def') //输出为:abcdef
    def fun1(a, b) {
        a
        b
        a+b
    }
    

    3.类

    在Groovy类中默认修饰符是public,没有default修饰符。
    Groovy会自动给类中的变量设置setter和getter方法(你当然可以自己手动重写它们),我们可以直接通过类名.成员来调用这些方法来对类中的成员变量进行访问和修改。(请注意Groovy自动设置的setter和getter不能通过方法名去调用它们,如果你确实要这样做,请在类中自定义这些方法)

    Person p = new Person()
    
    println "名字为:${p.name}" //默认为null,输出为:名字为:null
    println p.i //默认为0,输出为:0
    println p.b //默认为false,输出为:false
    p.name = 'Mike'
    println "名字为:${p.name}" //输出为:名字为:Mike
    
    class Person {
        private String name
        private boolean b
        private int i
    }
    

    你也可以手动重写getter方法:

    Person p = new Person()
    
    println "名字为:${p.name}" //输出为:名字为:abc
    p.name = 'Mike'
    println "名字为:${p.name}" //输出为:名字为:abc
    
    class Person {
        private String name
    
        public String getName() {
            return "abc"
        }
    }
    

    在类中可以直接通过成员名访问从父类继承来的私有成员,但是这种情况父类必须自己定义getter/setter方法

    class C1 {
        private int x = 5
        //必须包含这个方法,不然子类无法直接使用x
        int getX() {
            return x
        }
    }
    
    class C2 extends C1 {
        public test() {
            println x
        }
    }
    
    new C2().test() //5
    

    .运算符并不一定是操作类的成员变量的,其实质只是调用了setter/getter方法而已:

    User user = new User()
    println user.age //输出为:12
    //user.age = 10 User中没有定义setAge方法,所以会报错
    
    class User {
        public int getAge() {
            12
        }
    }
    

    在Gradle中有很多类似于这样的用法,其实类中并没有定义对应的属性,只不过是定义了相应的getter/setter方法而已

    三、Groovy闭包

    闭包是Groovy非常重要的一个特性,也是DSL的基础。闭包使Groovy省去了Java匿名内部类的繁琐,使代码变的灵活,轻量,以及可复用。

    <font face="微软雅黑" size = 4>闭包实质就是可以用作函数参数和方法参数的代码块,可以将这个代码块理解为一个函数指针</font>

    定义闭包

    闭包在Groovy中是groov.lang.Closure类,可以用Closure来声明,也可以用def来声明
    Groovy中闭包的定义如下:

    def xxx = {[params -> ] code} //记住 -> 是连在一起的,不能分开
    

    params可以理解为是函数的形参,形参名不能和外部变量同名,如果只有一个参数,可以将不指定参数,Groovy会指定一个隐式的默认参数it,如果没有参数,也可以不指定参数:

    Closure closure1 = {
        println it
    }
    
    Closure closure2 = {
        k,v ->
            println "${k} is ${v}"
    }
    

    调用闭包

    闭包可以直接当做一段代码来调用,通过闭包.call(参数)或者闭包(参数)来调用,也可以作为参数传递到函数中在调用。闭包跟方法一样,可以省略括号,用空格代替,当然无参闭包必须带上括号,否则会输出闭包的类对象

    closure1.call('Mike') //Mike
    closure2('name', 'Mike') //name is Mike
    closure2 'name', 'Mike' //name is Mike
    closure3.call() //There is no parameter
    

    需要注意的是,闭包是可以访问外部变量的,而函数不行

    String name = 'Jack'
    Closure closure4 = {
        println name
    }
    closure4() //Jack
    

    闭包跟函数一样,是有返回值的,默认最后一行语句是闭包的返回值,如果最后一行语句没有返回类型,那么返回null(比如定义变量或者直接给出常量,就是有返回值)

    //定义一个有返回值的闭包
    Closure closure5 = {
        'hello world'
    }
    //打印两个闭包的返回值,closure4最后一句是没有返回值的
    println closure4() //null
    println closure5() //hello world
    

    闭包可以视为一个普通的变量,所以闭包可以作为参数传递给函数,或者另一个闭包,也可以作为闭包的返回值返回

    def run1 = {a -> a * a}
    def run2 = {x, y -> y(x)}
    def run3 = {m -> {n -> m * n}}
    println run3(3)(run2(5,run1)) //输出为:75
    

    分析上段代码:

    1. 闭包run3传入了参数3,即m = 3,并返回一个闭包{n-> 3*n}
    2. 返回的闭包继续传入参数run2(5,run1),即n = run2(5,run1),返回一个数值为3*run2(5,run1)
    3. 继续看run2(5,run1),run2接受参数5和run1,返回值为run1(5),即为25
    4. 所以结果为75

    对于闭包有一些省略的写法:

    • 当匿名闭包作为参数时,如果它是最后一个参数,或者是唯一的参数,可以将闭包从括号中拉出来,放在括号后面:
    //之前的代码可以写成如下形式:
    println run3(3)(run2(5){a -> a * a}) //将run1从run2参数列表中拉出
    

    再看下List的each方法的写法:
    numList.each {println it}
    实质是List.each(closure),即真正写法是这样的:
    numList.each({println it})
    然后将闭包从参数列表中拉出:
    numList.each(){println it}
    在根据函数和闭包的参数写法可以省略括号,用空格代替:
    numList.each {println it}

    闭包的参数可以接受Map和List:

    • 闭包参数中与键值关系有关的参数,会自动封装起来放到闭包的第一个参数
    def x = {a, b, c -> a.x * a.y + b + c}
    println x(5, 7, x:2, y:3) //18
    
    • 如果闭包参数列表中本身没有List,那么传入List会将List中的元素依次匹配到参数列表
    def c = {a, b ,c -> a * b + c}
    def list = [1,2,3]
    println c(list) //5
    

    闭包委托

    委托策略是Groovy闭包中独有的语法,Groovy通过闭包委托策略使得DSL语言更加优化和简洁,在Gradle中就大量使用了闭包的委托策略。

    在抽象类groovy.lang.Closure中包含了三个成员变量:

      private Object delegate;
      private Object owner;
      private Object thisObject;
    

    三个成员含义如下:

    • thisObject指闭包所在的类,注意是类对象,并且是在闭包定义时的外围类对象,匿名闭包的thisObject并不是其调用者
    • owner指闭包上层的对象,即包含闭包的类或者闭包(多层嵌套的情况下)
    • delegate默认为owner

    根据前面讲的Groovy中类中成员的访问方式,Closure是抽象类,其中定义了对应的getter,delegate还设置了对应的setter方法。我们定义的都是它的实现子类,可以在闭包中通过三种成员名来直接访问这三种成员。

    class C1 {
        def firstClosure = {
            println "firstClosure.thisObject:${thisObject.getClass()}"
            println "firstClosure.owner:${owner.getClass()}"
            println "firstClosure.delegate:${delegate.getClass()}"
    
            def secondClosure = {
                println "secondClosure.thisObject:${thisObject.getClass()}"
                println "secondClosure.owner:${owner.getClass()}"
                println "secondClosure.delegate:${delegate.getClass()}"
            }
            secondClosure()
            new C2().test {
                println "test.thisObject:${thisObject.getClass()}"
                println "test.owner:${owner.getClass()}"
                println "test.delegate:${delegate.getClass()}"
            }
        }
    }
    
    class C2 {
        def test(Closure closure) {
            closure()
        }
    }
    new C1().firstClosure()
    

    输出如下:

    firstClosure.thisObject:class C1
    firstClosure.owner:class C1
    firstClosure.delegate:class C1
    secondClosure.thisObject:class C1
    secondClosure.owner:class C1$_closure1
    secondClosure.delegate:class C1$_closure1
    test.thisObject:class C1
    test.owner:class C1$_closure1
    test.delegate:class C1$_closure1
    

    Delegate策略

    当闭包中出现一个属性没有指定其所有者时,就会执行对应的Delegate策略:

    • OWNER_FIRST:这是默认的策略,会优先从owner中寻找对应的属性,如果找不到会去delegate中找
    • DELEGATE_FIRST:与OWNER_FIRST相反。
    • OWNER_ONLY:只在owner中寻找
    • DELEGATE_ONLY:只在delegate中寻找
    • TO_SELF:只在闭包自身中找

    看如下的例子:

    class User {
        String name
    
        def c1 = {
            //def name = 100 
            println name
        }
    
    }
    
    class Person {
        String name
    
        def test(Closure c) {
            c()
        }
    }
    String name = 'Wrapper'
    User u = new User()
    Person p = new Person()
    u.name = 'user'
    p.name = 'person'
    p.test(u.c1) //user
    u.c1.setResolveStrategy(Closure.DELEGATE_FIRST)
    u.c1.delegate = p
    p.test(u.c1) //person
    p.test {
        println name
    } //Wrapper
    

    上述代码,如果在闭包中设置了name = 100,那么闭包调用时不会调用委托策略,而是直接输出100
    第一次输出user是因为默认从owner中找name属性
    第二次输出person是因为设置了优先从delegate中寻找name属性
    第三次输出Wrapper是因为匿名闭包的owner是外部的类,而非其调用者,所以输出Wrapper

    在Gradle中基本上都是用Delegate策略使用闭包来对项目进行配置属性的

    ps:第一次写博客,请多多指教!

    相关文章

      网友评论

        本文标题:Android Gradle(一)——Groovy基础

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