Groovy介绍

https://www.jianshu.com/p/7af24e1982e7

 

参考:
深入理解Android之Gradle
Groovy与Java的区别
Groovy语法
Groovy基础——Closure(闭包)详解

def varAndMethod() {
    def a = 1  //不显式声明变量类型
    a = "abc"  //运行时改变类型
    println a  //结尾无需 ;
    a = 4      //最后一行作为返回值
}
def ret = varAndMethod()
println ret    //输出4

Groovy是一种动态语言。对自己的定义就是:Groovy是在 java平台上的、 具有像Python, Ruby 和 Smalltalk 语言特性的灵活动态语言。
除了语言和Java相通外,Groovy有时候又像一种脚本语言。

下面我们将介绍Groovy。由于此文的主要目的是Gradle,所以我们不会过多讨论Groovy中细枝末节的东西,而是把知识点集中在以后和Gradle打交道时一些常用的地方上。

一 前提知识

1.Groovy注释标记和Java一样,支持//或者/**/
2.Groovy语句可以不用分号结尾。Groovy为了尽量减少代码的输入,确实煞费苦心
3.Groovy中支持动态类型,即定义变量的时候可以不指定其类型。
4.Groovy中,变量定义可以使用关键字def。注意,虽然def不是必须的,但是为了代码清晰,
  建议还是使用def关键字
  def i = 1       //可以不使用分号结尾
  def ss = "I ama person"
  def  int x = 1  //变量定义时,也可以直接指定类型
5.函数定义时,参数的类型也可以不指定。比如
  String testFunction(arg1,arg2){ //无需指定参数类型
    ...
  }  
6.除了变量定义可以不指定类型外,Groovy中函数的返回值也可以是无类型的。比如:
  //无类型的函数定义,必须使用def关键字
  def  nonReturnTypeFunc(){
      last_line   //最后一行代码的执行结果就是本函数的返回值
  }
  //如果指定了函数返回类型,则可不必加def关键字来定义函数
  String getString(){
     return"I am a string"
  }
7.函数返回值:Groovy的函数里,可以不使用returnxxx来设置xxx为函数返回值。
  如果不使用return语句的话,则函数里最后一句代码的执行结果被设置成返回值。比如
  //下面这个函数的返回值是字符串"getSomething return value"
  def getSomething(){
       "I am a return value" //最后一行代码,返回类型为String
  }
  注意,如果函数定义时候指明了返回值类型的话,函数中则必须返回正确的数据类型。
8.最后,除了每行代码不用加分号外,Groovy中函数调用的时候还可以不加括号。比如:
  println("test") ---> println"test"
  注意,虽然写代码的时候,对于函数调用可以不带括号,但是Groovy经常把属性和函数混淆。比如
  def method(){
    "hello"
  }
  method  //如果不加括号的话,Groovy会误认为method是一个变量。找不到就会报错:
  所以调用函数最好带上括号,也好理解一些,如果这个函数是Groovy API
  或者Gradle API中比较常用的,比如println,就可以不带括号。

Groovy中的字符串

Groovy对字符串支持相当强大,充分吸收了一些脚本语言的优点:
1  单引号''中的内容严格对应Java中的String,不对$符号进行转义
   defsingleQuote='I am $ dolloar'  //输出就是I am $ dolloar
2  双引号""的内容则和脚本语言的处理有点像,如果字符中有$号的话,则它会$表达式先求值。
   def s1 = "I am one dollar" //输出 I am one dollar
   def x = 1
   def s2 = "I am $x dolloar" //输出I am 1 dolloar
3 三个引号'''xxx'''中的字符串支持随意换行 比如
   defmultieLines = ''' begin
     line  1
     line  2
     end '''

二 Groovy中的数据类型

主要有3种:

  • 一个是Java中的基本数据类型。
  • 另外一个是Groovy中的容器类。
  • 最后一个非常重要的是闭包。

基本数据类型

  • 作为动态语言,Groovy世界中的所有事物都是对象。所以,int,boolean这些Java中的基本数据类型,在Groovy代码中其实对应的是它们的包装数据类型,比如int对应为Integer,boolean对应为Boolean,因此可以对变量直接调用方法:
def int x = 4
y = 3
assert x.plus(y) == 7

闭包

闭包,英文叫Closure,是Groovy中非常重要的一个数据类型或者说一种概念了。闭包的历史来源,种种好处我就不说了。我们直接看怎么使用它!

  1. 闭包,是一种数据类型,它代表了一段可执行的代码。其外形如下:
def aClosure = {  //闭包是一段代码,用花括号括起来..
    String param1, int param2 ->  //这个箭头很关键。
                        //箭头前面是参数定义,箭头后面是代码块
    println"this is code" //这是代码块,最后一句是返回值,
                //也可以使用return,和Groovy中普通函数一样
}

简而言之,Closure的定义格式是:

def xxx = {paramters -> code}  //或者  
def xxx = {无参数纯code}  这种case不需要->符号

闭包定义好后,要调用它的方法就是:
--闭包对象.call(参数) 或者:
--闭包对象(参数)

aClosure.call("this is string",100)  或者
aClosure("this is string", 100)

上面就是一个闭包的定义和使用。

  1. 在闭包中,还需要注意一点:
    如果闭包没定义参数的话,则隐含有一个参数,这个参数名字叫it,和this的作用类似。it代表闭包的参数。比如:
def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'
等同于:
def greeting = { it -> "Hello, $it!"}
assert greeting('Patrick') == 'Hello, Patrick!'

但是,如果在闭包定义时,采用下面这种写法,则表示闭包没有参数!

def noParamClosure = { -> true }
这个时候,我们就不能给noParamClosure传参数了!
noParamClosure ("test")  <==报错喔!
  1. 省略圆括号
    Groovy中,当函数的最后一个参数是闭包的话,可以省略圆括号。
    闭包在Groovy中大量使用,比如很多类都定义了一些函数,这些函数最后一个参数都是一个闭包。比如:
public static <T> List<T>each(List<T> self, Closure closure)
//上面这个函数表示针对List的每一个元素都会调用closure做一些处理。
//这里的closure,就有点回调函数的感觉。但是,在使用这个each函数的时候,
//我们传递一个怎样的Closure进去呢?比如:
def iamList = [1,2,3,4,5]  //定义一个List
iamList.each{ //调用它的each,这段代码的格式看不懂了吧?each是个函数,圆括号去哪了?
      println it
}
上面代码有两个知识点:
    1) each函数调用的圆括号不见了!原来,Groovy中,当函数的最后一个参数是闭包的话,可以省略圆括号。比如
def testClosure(int a1,String b1, Closure closure){
      //dosomething
     closure() //调用闭包
}
那么调用的时候,就可以免括号!
testClosure (4, "test", {
      println"i am in closure"
} )  //括号可以不写..

注意,这个特点非常关键,因为以后在Gradle中经常会出现图7这样的代码:

 
闭包调用

经常碰见这样的没有圆括号的代码。省略圆括号虽然使得代码简洁,但是却使代码很难理解,完整的代码应该按下面这种写法:

 doLast({
   println'Hello world!'
 })

有了圆括号,你会知道 doLast只是把一个Closure对象传了进去。很明显,它不代表这段脚本解析到doLast的时候就会调用println ‘Hello world!’ 。但是把圆括号去掉后,就感觉好像println ‘Hello world!’立即就会被调用一样!

容器类

Groovy中的容器类很简单,就三种:

  • List:链表,其底层对应Java中的List接口,一般用ArrayList作为真正的实现类。
  • Map:键-值表,其底层对应Java中的LinkedHashMap。
  • Range:范围,它其实是List的一种拓展。

对容器而言,我们最重要的是了解它们的用法。下面是一些简单的例子:

  • List类
变量定义:List变量由[]定义,比如
def aList = [5,'string',true] //List由[]定义,其元素可以是任何对象
//变量存取:可以直接通过索引存取,而且不用担心索引越界。
//如果索引超过当前链表长度,List会自动
往该索引添加元素
assert aList[1] == 'string'
assert aList[5] == null //第6个元素为空
aList[100] = 100 //设置第101个元素的值为10
assert aList[100] == 100
那么,aList到现在为止有多少个元素呢?
println aList.size  ===>结果是101
  • Map类
容器变量定义
1.变量定义:Map变量由[:]定义,比如
      def aMap = ['key1':'value1','key2':true]
      注意其中的冒号。冒号左边是key,右边是Value。
2.key必须是字符串,value可以是任何对象。
      另外,key可以用''或""包起来,也可以不用引号包起来。比如
      def aNewMap = [key1:"value",key2:true]
      其中的key1和key2默认被处理成字符串"key1"和"key2",
      但最好是用""引起来,避免引起混淆。比如:

      def key1="wowo"
      def aConfusedMap=[key1:"who am i?"]
      aConfuseMap中的key1到底是"key1"还是变量key1的值“wow”?
      显然,答案是字符串"key1"。如果要是"wowo"的话,则aConfusedMap的定义必须设置成:
      def aConfusedMap=[(key1):"who am i?"]
3.Map中元素的存取更加方便,它支持多种方法:
      println aMap.keyName    <==这种表达方法好像key就是aMap的一个成员变量一样
      println aMap['keyName'] <==这种表达方法更传统一点
      aMap.anotherkey = "i am map"  <==为map添加新元素
  • Range类
Range是Groovy对List的一种拓展,变量定义和大体的使用方法如下:
def aRange = 1..5 <==Range类型的变量 由begin值+两个点+end值表示
                      左边这个aRange包含1,2,3,4,5这5个值
如果不想包含最后一个元素,则
def aRangeWithoutEnd = 1..<5  <==包含1,2,3,4这4个元素
println aRange.from
println aRange.to

多嘴一句,文档中并没有说明Range有from和to这两个属性,但是却有getFrom和getTo这两个函数。根据Groovy的原则,如果一个类中有名为xxyyzz这样的属性(其实就是成员变量),Groovy会自动为它添加getXxyyzz和setXxyyzz两个函数,用于获取和设置xxyyzz属性值。
注意,get和set后第一个字母是大写的
所以,当你看到Range中有getFrom和getTo这两个函数时候,就得知道潜规则下,Range有from和to这两个属性。当然,由于它们不可以被外界设置,所以没有公开setFrom和setTo函数。

三 变量的作用域

def x = 1 <==注意,这个x有def(或者指明类型,比如 int x = 1)
def printx(){
   println x
}

printx() <==报错,说x找不到,生成的java代码如下:

源码
源码

1.printx被定义成test类的成员函数
2.def x = 1,这句话是在run方法中创建的。所以,x=1从代码上看好像是在整个脚本中定义的,但实际上printx访问不了它。printx是test成员函数,除非x也被定义成test的成员函数,否则printx不能访问它。

 

那么,如何使得printx能访问x呢?很简单,定义的时候不要加类型和def。即:

x = 1 <==注意,去掉def和类型
def printx(){
   println x
}

现在生成的java代码如下:

 
源码

比较复杂,x也没有被定义成test的成员变量,而是在run的执行过程中,将x作为一个属性添加到test实例对象中了。然后在printx中,先获取这个属性。
注意,Groovy的文档说 x = 1这种定义将使得x变成test的成员变量,但从反编译情况看,这是不对得…..
虽然printx可以访问x变量了,但是别的脚本却无法访问x变量。因为它不是test的成员变量。

 

那么如何才能定义成成员变量呢:

import groovy.transform.Field;   //必须要先import

查看编译后的test.class文件,得到:


源码
源码

四 文件操作

Groovy的I/O操作是在原有Java I/O操作上进行了更为简单方便的封装,并且使用Closure来简化代码编写。主要封装了如下一些了类:


 
封装类

java.io.File: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/File.html
java.io.InputStream:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/InputStream.html
java.io.OutputStream:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/OutputStream.html
java.io.Reader:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Reader.html
java.io.Writer:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Writer.html
java.nio.file.Path:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/nio/file/Path.html

读文件
def targetFile = new File(文件名) <==File对象还是要创建的。
然后打开File的API看看Groovy定义的API:

读该文件中的每一行的方法:eachLine的唯一参数是一个Closure。Closure的参数是文件每一行的内容及行数
其内部实现肯定是Groovy打开这个文件,然后读取文件的一行,然后调用Closure…

 targetFile.eachLine{ 
   String oneLine,int lineNum ->
    println oneLine    
}  <==是不是令人发指??!

不用处理关闭流之类的,Groovy已经帮处理了

直接得到文件内容:
targetFile.getText() 或者
targetFile.getBytes() <==文件内容一次性读出

流操作

1. 直接操作inputStream
def ism =  targetFile.newInputStream()
//操作ism,最后记得关掉
ism.close

2. 使用闭包操作inputStream,以后在Gradle里会常看到这种搞法
 targetFile.withInputStream{ ism ->
   ...//操作ism. 不用close。Groovy会自动替你close
}
//file.withInputStream方法参考API可知参数
//就一个Closure,回创建一个本文件的输
//入流并作为参数传递给Closure
写文件
def targetFile = new File(目标文件名)
targetFile.withOutputStream{ os->
  os.setBytes(内容)
}

XML操作
除了I/O异常简单之外,Groovy中的XML操作也极致得很。Groovy中,XML的解析提供了和XPath类似的方法,名为GPath。这是一个类,提供相应API。
GPath功能包括:给个例子好了,来自Groovy官方文档。

test.xml文件:
<response version-api="2.0">
       <value>
           <books>
               <book available="20" id="1">
                   <title>Don Xijote</title>
                   <author id="1">Manuel De Cervantes</author>
               </book>
               <book available="14" id="2">
                   <title>Catcher in the Rye</title>
                  <author id="2">JD Salinger</author>
              </book>
              <book available="13" id="3">
                  <title>Alice in Wonderland</title>
                  <author id="3">Lewis Carroll</author>
              </book>
              <book available="5" id="4">
                  <title>Don Xijote</title>
                  <author id="4">Manuel De Cervantes</author>
              </book>
           </books>
      </value>
   </response>

现在来看怎么玩转GPath:

//第一步,创建XmlSlurper类
def xparser = new XmlSlurper()
def targetFile = new File("test.xml")
//轰轰的GPath出场
GPathResult gpathResult =xparser.parse(targetFile)
 
//开始玩test.xml。现在我要访问id=4的book元素。
//下面这种搞法,gpathResult代表根元素response。通过e1.e2.e3这种
//格式就能访问到各级子元素....
def book4 = gpathResult.value.books.book[3]
//得到book4的author元素
def author = book4.author
//再来获取元素的属性和textvalue
assert author.text() == ' Manuel De Cervantes '
获取属性更直观
author.

好了。GPath就说到这。再看个例子。我在使用Gradle的时候有个需求,就是获取AndroidManifest.xml版本号(versionName)。有了GPath,一行代码搞定,请看:

def androidManifest = newXmlSlurper().parse("AndroidManifest.xml")
println androidManifest['@android:versionName']
或者
println androidManifest.@'android:versionName'

-> setting.gradle文件
def initXXX(){  
    println"initialize  ....."  
    ......//干一些special的私活....   
}  
//settings.gradle加载的时候,会执行initMinshengGradleEnvironment  
initXXX()  
include 'youku' , 'alipay' , 'baidu'
apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
  Properties properties = new Properties()  
  File propertyFile = new File(rootDir.getAbsolutePath() +"/local.properties")  
   properties.load(propertyFile.newDataInputStream())  
    //gradle就是gradle对象。它默认是Settings和Project的成员变量。可直接获取  
   //ext前缀,表明操作的是外置属性。api是一个新的属性名。前面说过,只在  
   //第一次定义或者设置它的时候需要ext前缀  
    gradle.ext.api =properties.getProperty('sdk.api')  
     
    println gradle.api  //再次存取api的时候,就不需要ext前缀了  


//utils.gradle中定义了一个获取AndroidManifests.xml的versionName的方法
def  getVersionNameAdvanced(){
�   //下面这行代码中的project是谁?
   defxmlFile = project.file("AndroidManifest.xml")
   defrootManifest = new XmlSlurper().parse(xmlFile)
   return rootManifest['@android:versionName']  
}
//现在,想把这个方法输出到各个Project。由于这个utils.gradle会被每一个Project Apply,所以
ext{   
    //除了ext.xxx=value这种定义方法外,还可以使用ext{}这种书写方法。
    getVersionNameAdvanced = this.&getVersionNameAdvanced
 }


作者:蒸汽飞船
链接:https://www.jianshu.com/p/7af24e1982e7
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

posted on 2024-02-01 11:38  ExplorerMan  阅读(72)  评论(0编辑  收藏  举报

导航