1. 程式人生 > >Gradle Groovy 基礎語法 MD

Gradle Groovy 基礎語法 MD

Markdown版本筆記 我的GitHub首頁 我的部落格 我的微信 我的郵箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 [email protected]

Gradle Groovy 基礎語法 MD


目錄

目錄
Groovy 基礎
為何要學 Groovy
為何要使用 Groovy
如何編譯執行 Groovy

最基本的語法
支援的資料型別
String
閉包
List和Map
閉包的引數
加強的IO
訪問xml檔案
其他的一些語法特性
Getter和Setter
構造器
Class型別
使用with()操作符
判斷是否為真
簡潔的三元表示式
捕獲任何異常
簡潔的非空判斷
使用斷言
==和equals
switch方法
字串分行
Import 別名

Groovy 基礎

docs文件
api文件
參考1
參考2

為何要學 Groovy

Gradle是目前Android主流的構建工具,不管你是通過命令列還是通過AndroidStudio來build,最終都是通過Gradle來實現的。所以學習Gradle非常重要。

目前國內對Android領域的探索已經越來越深,不少技術領域如外掛化、熱修復、構建系統等都對Gradle有迫切的需求,不懂Gradle將無法完成上述事情。所以Gradle必須要學習。

Gradle不單單是一個配置指令碼,它的背後是幾門語言:

  • Groovy Language
  • Gradle DSL
  • Android DSL

DSL的全稱是Domain Specific Language,即領域特定語言,或者直接翻譯成“特定領域的語言”,再直接點,其實就是這個語言不通用,只能用於特定的某個領域,俗稱“小語言”。因此DSL也是語言。

實際上,Gradle指令碼大多都是使用groovy語言編寫的。

Groovy是一門jvm語言,功能比較強大,細節也很多,全部學習的話比較耗時,對我們來說收益較小,並且玩轉Gradle並不需要學習Groovy的全部細節,所以其實我們只需要學一些Groovy基礎語法與API即可。

為何要使用 Groovy

Groovy是一種基於JVM的敏捷開發語言,它結合了眾多指令碼語言的強大的特性,由於同時又能與Java程式碼很好的結合。一句話:既有面向物件的特性又有純粹的指令碼語言的特性。

由於Groovy執行在JVM上,因此也可以使用Java語言編寫的組建。

簡單來說,Groovy提供了更加靈活簡單的語法大量的語法糖以及閉包特性可以讓你用更少的程式碼來實現和Java同樣的功能

如何編譯執行 Groovy

Groovy是一門jvm語言,它最終是要編譯成class檔案然後在jvm上執行,所以Java語言的特性Groovy都支援,Groovy支援99%的java語法,我們完全可以在Groovy程式碼中直接貼上java程式碼。

可以安裝Groovy sdk來編譯和執行。但是我並不想搞那麼麻煩,畢竟我們的最終目的只是學習Gradle。

推薦大家通過這種方式來編譯和執行Groovy。

在當面目錄下建立build.gradle檔案,在裡面建立一個task,然後在task中編寫Groovy程式碼即可,如下所示:

task(testGroovy).doLast {
   println "開始執行自定義task"
   test()
}

def test() {
   println "執行Groovy語法的程式碼"
   System.out.println("執行Java語法的程式碼!");
}

然後在命令列終端中執行如下命令即可:

gradle testGroovy
> Configure project :app 

> Task :app:testGroovy 
開始執行自定義task
執行Groovy語法的程式碼
執行Java語法的程式碼!

BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed

我們知道,在Android專案中,我們只要更改build.gradle檔案一點內容,AS就會提示我們同步:

但是在我們測試 Groovy 時中,我們更改build.gradle檔案後可以不必去同步,執行命令時會自動執行你修改後的最新邏輯。

最基本的語法

Groovy中的類和方法預設都是public許可權的,所以我們可以省略public關鍵字,除非我們想使用private。
Groovy中的型別是弱化的,所有的型別都可以動態推斷,但是Groovy仍然是強型別的語言,型別不匹配仍然會報錯。
Groovy中通過 def 關鍵字來宣告變數和方法
Groovy中很多東西都是可以省略的,比如

  • 語句後面的分號是可以省略的
  • def 和 變數的型別 中的其中之一是可以省略的(不能全部省略)
  • def 和 方法的返回值型別 中的其中之一是可以省略的(不能全部省略)
  • 方法呼叫時的圓括號是可以省略的
  • 方法宣告中的引數型別是可以省略的
  • 方法的最後一句表示式可作為返回值返回,而不需要return關鍵字

    省略return關鍵字並不是一個好的習慣,就如同 if else while 後面只有一行語句時可以省略大括號一樣,以後如果添加了其他語句,很有可能會導致邏輯錯誤

def int a = 1; //如果 def 和 型別同時存在,IDE 會提示你"def是不需要的(is unnecessary)"
def String b = "hello world" //省略分號,存在分號時也會提示你 unnecessary
def c = 1 //省略型別

def hello() {  //省略方法宣告中的返回值型別
   println ("hello world");
   println "hello groovy" //省略方法呼叫時的圓括號
   return 1;
}

def hello(String msg) {
   println "hello" + msg //省略方法呼叫時的圓括號
   1;                    //省略return
}

int hello(msg) { //省略方法宣告中的引數型別
   println msg
   return 1 // 這個return不能省略
   println "done" //這一行程式碼是執行不到的,IDE 會提示你 Unreachable statement,但語法沒錯
}

支援的資料型別

在Groovy中,資料型別有:

  • Java中的基本資料型別
  • Java中的物件
  • Closure(閉包)
  • 加強的List、Map等集合型別
  • 加強的File、Stream等IO型別

型別可以顯示宣告,也可以用 def 來宣告,用 def 宣告的型別Groovy將會進行型別推斷。
基本資料型別和物件和Java中的一致,只不過在Gradle中,物件預設的修飾符為public

String

String的特色在於字串的拼接,比如

def a = 1
def b = "hello"
def c = "a=${a}, b=${b}"
println c //a=1, b=hello

閉包

Groovy中有一種特殊的型別,叫做Closure,翻譯過來就是閉包,這是一種類似於C語言中函式指標的東西。
閉包用起來非常方便,在Groovy中,閉包作為一種特殊的資料型別而存在,閉包可以作為方法的引數和返回值,也可以作為一個變數而存在
閉包可以有返回值和引數,當然也可以沒有。下面是幾個具體的例子:

def test() {
    def closure = { String parameters -> //閉包的基本格式
        println parameters
    }
    def closure2 = { a, b -> // 省略了閉包的引數型別
        println "a=${a}, b=${b}"
    }
    def closure3 = { a ->
        a + 1 //省略了return
    }
    def closure4 = { // 省略了閉包的引數宣告
        println "引數為 ${it}" //如果閉包不指定引數,那麼它會有一個隱含的引數 it
    }

    closure("包青天") //包青天
    closure2 10086, "包青天" //a=10086, b=包青天
    println closure3(1) //2
    //println closure3 2 //不允許省略圓括號,會提示:Cannot get property '1' on null object
    closure4() //引數為 null
    closure4 //不允許省略圓括號,但是並不會報錯
    closure4 10086 //引數為 10086
}

閉包的一個難題是如何確定閉包的引數(包括引數的個數、引數的型別、引數的意義),尤其當我們呼叫Groovy的API時,這個時候沒有其他辦法,只有查詢Groovy的文件才能知道。

List和Map

Groovy加強了Java中的集合類,比如List、Map、Set等。

基本使用如下:

def emptyList = []
def list = [10086, "hello", true]
list[1] = "world"
assert list[1] == "world"
println list[0] //10086
list << 5 //相當於 add()
assert 5 in list // 呼叫包含方法
println list //[10086, world, true, 5]
def range = 1..5
assert 2 in range
println range //1..5
println range.size() //5
def emptyMap = [:]
def map = ["id": 1, "name": "包青天"]
map << [age: 29] //新增元素
map["id"] = 10086 //訪問元素方式一
map.name = "哈哈" //訪問元素方式二,這種方式最簡單
println map //{id=10086, name=哈哈, age=29}

可以看到,通過Groovy來操作List和Map顯然比Java簡單的多。

上面有一個看起來很奇怪的操作符<<,其實這並沒有什麼大不了,<<表示向List中新增新元素的意思,這一點從 List文件 當也能查到。

public List leftShift(Object value)
  • Overloads the left shift operator to provide an easy way to append objects to a List. 過載左移位運算子,以提供將物件append到List的簡單方法。
  • Parameters: value - an Object to be added to the List.
  • Returns: same List, after the value was added to it.

實際上,這個運算子是大量使用的,並且當你用 leftShift 方法時 IDE 也會提示你讓你使用左移位運算子<<替換:

def list = [1, 2]
list.leftShift 3
assert list == [1, 2, 3]
list << 4
println list //[1, 2, 3, 4]

閉包的引數

這裡藉助Map再講述下如何確定閉包的引數。比如我們想遍歷一個Map,我們想採用Groovy的方式,通過檢視文件,發現它有如下兩個方法,看起來和遍歷有關:

  • Map each(Closure closure):Allows a Map to be iterated through using a closure.
  • Map eachWithIndex(Closure closure):Allows a Map to be iterated through using a closure.

可以發現,這兩個each方法的引數都是一個閉包,那麼我們如何知道閉包的引數呢?當然不能靠猜,還是要查文件。

public Map each(Closure closure)
  • Allows a Map to be iterated through using a closure. If the closure takes one parameter then it will be passed the Map.Entry otherwise if the closure takes two parameters then it will be passed the key and the value.
  • In general, the order in which the map contents are processed cannot be guaranteed(通常無法保證處理元素的順序). In practise(在實踐中), specialized forms of Map(特殊形式的Map), e.g. a TreeMap will have its contents processed according to the natural ordering(自然順序) of the map.
def result = ""
[a:1, b:3].each { key, value -> result += "$key$value" } //兩個引數
assert result == "a1b3"
def result = ""
[a:1, b:3].each { entry -> result += entry } //一個引數
assert result == "a=1b=3"

[a: 1, b: 3].each { println "[${it.key} : ${it.value}]" } //一個隱含的引數 it,key 和 value 是屬性名

試想一下,如果你不知道查文件,你又怎麼知道each方法如何使用呢?光靠從網上搜,API文件中那麼多介面,搜的過來嗎?記得住嗎?

加強的IO

在Groovy中,檔案訪問要比Java簡單的多,不管是普通檔案還是xml檔案。怎麼使用呢?查來 File文件

public Object eachLine(Closure closure)
  • Iterates through this file line by line. Each line is passed to the given 1 or 2 arg closure. The file is read using a reader which is closed before this method returns.
  • Parameters: closure - a closure (arg 1 is line, optional arg 2 is line number starting at line 1)
  • Returns: the last value returned by the closure

可以看到,eachLine方法也是支援1個或2個引數的,這兩個引數分別是什麼意思,就需要我們學會讀文件了,一味地從網上搜例子,多累啊,而且很難徹底掌握:

def file = new File("a.txt")
file.eachLine { line, lineNo ->
    println "${lineNo} ${line}" //行號,內容
}

file.eachLine { line ->
    println "${line}" //內容
}

除了eachLine,File還提供了很多Java所沒有的方法,大家需要瀏覽下大概有哪些方法,然後需要用的時候再去查就行了,這就是學習Groovy的正道。

訪問xml檔案

Groovy訪問xml有兩個類:XmlParserXmlSlurper,二者幾乎一樣,在效能上有細微的差別,不過這對於本文不重要。
groovy.util.XmlParserAPI文件

文件中的案例:

def xml = '<root><one a1="uno!"/><two>Some text!</two></root>'
//或者 def xml = new XmlParser().parse(new File("filePath.xml"))
def rootNode = new XmlParser().parseText(xml) //根節點
assert rootNode.name() == 'root' //根節點的名稱
assert rootNode.one[0][email protected] == 'uno!' //根節點中的子節點 one 的 a1 屬性的值
assert rootNode.two.text() == 'Some text!'  //根節點中的子節點 two 的內容
rootNode.children().each { assert it.name() in ['one','two'] }

更多的細節查文件即可。

其他的一些語法特性

Getter和Setter

當你在Groovy中建立一個beans的時候,通常我們稱為POGOS(Plain Old Groovy Objects),Groovy會自動幫我們建立getter/setter方法。
當你對getter/setter方法有特殊要求,你儘可提供自己的方法,Groovy預設的getter/setter方法會被替換。

構造器

有一個bean

class Server {
    String name
    Cluster cluster
}

初始化一個例項的時候你可能會這樣寫:

def server = new Server()
server.name = "Obelix"
server.cluster = aCluster

其實你可以用帶命名的引數的預設構造器,會大大減少程式碼量:

def server = new Server(name: "Obelix", cluster: aCluster)

Class型別

在Groovy中Class型別的.class字尾不是必須的,比如:

def func(Class clazz) {
    println clazz
}
func(File.class) //class java.io.File
func(File) //class java.io.File

使用with()操作符

當更新一個例項的時候,你可以使用with()來省略相同的字首,比如:

Book book = new Book() 
book.with {
   id = 1 //等價於 book.id = 1
   name = "包青天"
   start(10086)
   stop("包青天")
}

判斷是否為真

所有型別都能轉成布林值,比如nullvoid相當於0或者相當於false,其他則相當於true,所以:

if (name) {}
//等價於
if (name != null && name.length > 0) {}

在Groovy中可以在類中新增asBoolean()方法來自定義是否為真

簡潔的三元表示式

在Groovy中,三元表示式可以更加簡潔,比如:

def result = name ?: ""
//等價於
def result = name != null ? name : ""

捕獲任何異常

如果你實在不想關心try塊裡丟擲何種異常,你可以簡單的捕獲所有異常,並且可以省略異常型別:

try {
    // ...
} catch (any) { //可以省略異常型別
    // something bad happens
}

這裡的any並不包括Throwable,如果你真想捕獲everything,你必須明確的標明你想捕獲Throwable

簡潔的非空判斷

在java中,你要獲取某個物件的值必須要檢查是否為null,這就造成了大量的if語句;在Groovy中,非空判斷可以用?.表示式,比如:

println order?.customer?.address
//等價於
if (order != null) {
   if (order.getCustomer() != null) {
       if (order.getCustomer().getAddress() != null) {
           System.out.println(order.getCustomer().getAddress());
       }
   }
}

使用斷言

在Groovy中,可以使用assert來設定斷言,當斷言的條件為false時,程式將會丟擲異常

def check(String name) {
   assert name // 檢查方法傳入的引數是否為空,name non-null and non-empty according to Groovy Truth
   assert name?.size() > 3
}

==和equals

Groovy裡的is()方法等同於Java裡的==
Groovy中的==是更智慧的equals(),比較兩個類的時候,你應該使用a.is(b)而不是==
Groovy中的==可以自動避免NullPointerException異常

status == "包青天"
//等價於Java中的
status != null && status.equals("包青天")

switch方法

在Groovy中,switch方法變得更加靈活,可以同時支援更多的引數型別:

def x = null
def result = ""
switch (x) {
    case "foo": result = "found foo" //沒有 break 時會繼續向下判斷
    case "bar": result += "bar"
        break
    case [4, 5, 6]: result = "list" //匹配集合中的元素
        break
    case 12..30: result = "range" //匹配某個範圍內的元素
        break
    case Integer: result = "integer" //匹配Integer型別
        break
    case { it > 3 }: result = "number > 3" //匹配表示式
        break
    case Number: result = "number" //匹配Number型別
        break
    default: result = "default"
}
println result

字串分行

Java中,字串過長需要換行時我們一般會這樣寫:

throw new PluginException("Failed to execute command list-applications:" +
    " The group with name " +
    parameterMap.groupname[0] +
    " is not compatible group of type " +
    SERVER_TYPE_NAME)

Groovy中你可以用 \ 字元,而不需要新增一堆的雙引號:

throw new PluginException("Failed to execute command list-applications: \
The group with name ${parameterMap.groupname[0]} \
is not compatible group of type ${SERVER_TYPE_NAME}")

或者使用多行字串""":

throw new PluginException("""Failed to execute command list-applications:
    The group with name ${parameterMap.groupname[0]}
    is not compatible group of type ${SERVER_TYPE_NAME)}""")

Groovy中,單引號引起來的字串是java字串,不能使用佔位符來替換變數,雙引號引起的字串則是java字串或者Groovy字串。

Import 別名

在java中使用兩個類名相同但包名不同的兩個類,像java.util.Listjava.wt.List,你必須使用完整的包名才能區分。Groovy中則可以使用import別名:

import java.util.List as jurist //使用別名
import java.awt.List as aList
import java.awt.WindowConstants as WC
import static pkg.SomeClass.foo //靜態引入方法

2019-1-12