1. 程式人生 > >Gradle 詳解

Gradle 詳解

Gradle是當前非常“勁爆”的構建工具。本篇文章就是專為講解Gradle而來。介紹Gradle之前,先說點題外話。

一、題外話

說實話,我在索尼工作的時候,就見過Gradle。但是當時我一直不知道這是什麼東西。而且索尼工具組的工程師還將其和Android Studio索尼版一起推送,偶一看就更沒興趣了。為什麼那個時候如此不待見Gradle呢?因為我此前一直是做ROM開發。在這個層面上,我們用make,mm或者mmm就可以了。而且,編譯耗時對我們來說也不是啥痛點,因為用組內吊炸天的神機伺服器完整編譯索尼的image也要耗費1個小時左右。所以,那個時侯Gradle完全不是我們的菜。

現在,搞APP開發居多,編譯/打包等問題立即就成痛點了。比如:

  • 一個APP有多個版本,Release版、Debug版、Test版。甚至針對不同APP Store都有不同的版本。在以前ROM的環境下,雖然可以配置Android.mk,但是需要依賴整個Android原始碼,而且還不能完全做到滿足條件,很多事情需要手動搞。一個app如果涉及到多個開發者,手動操作必然會帶來混亂。
  • library工程我們需要編譯成jar包,然後釋出給其他開發者使用。以前是用eclipse的export,做一堆選擇。要是能自動編譯成jar包就爽了。

上述問題對絕大部分APP開發者而言都不陌生,而 Gradle

 作為一種很方便的的構建工具,可以非常輕鬆得解決構建過程中的各種問題。

二、閒言構建

構建,叫build也好,叫make也行。反正就是根據輸入資訊然後幹一堆事情,最後得到幾個產出物(Artifact)。

最最簡單的構建工具就是make了。make就是根據Makefile檔案中寫的規則,執行對應的命令,然後得到目標產物。

日常生活中,和構建最類似的一個場景就是做菜。輸入各種食材,然後按固定的工序,最後得到一盤菜。當然,做同樣一道菜,由於需求不同,做出來的東西也不盡相同。比如,宮保雞丁這道菜,回民要求不能放大油、口淡的要求少放鹽和各種油、辣不怕的男女漢子們可以要求多放辣子....總之,做菜包含固定的工序,但是對於不同條件或需求,需要做不同的處理。

在Gradle爆紅之前,常用的構建工具是ANT,然後又進化到Maven。ANT和Maven這兩個工具其實也還算方便,現在還有很多地方在使用。但是二者都有一些缺點,所以讓更懶得人覺得不是那麼方便。比如,Maven編譯規則是用XML來編寫的。XML雖然通俗易懂,但是很難在xml中描述if{某條件成立,編譯某檔案}/else{編譯其他檔案} 這樣有不同條件的任務。

怎麼解決?怎麼解決好?對程式設計師而言,自然是程式設計解決,但是有幾個小要求:

  • 這種“程式設計”不要搞得和程式設計師理解的程式設計那樣複雜。寥寥幾筆,輕輕鬆鬆把要做的事情描述出來就最好不過。所以,Gradle選擇了Groovy。Groovy基於Java並拓展了Java。 Java程式設計師可以無縫切換到使用Groovy開發程式。 Groovy說白了就是把寫Java程式變得像寫指令碼一樣簡單。寫完就可以執行,Groovy內部會將其編譯成Java class然後啟動虛擬機器來執行。當然,這些底層的渣活不需要你管。
  • 除了可以用很靈活的語言來寫構建規則外,Gradle另外一個特點就是它是一種DSL,即Domain Specific Language ,領域相關語言。什麼是DSL,說白了它是某個行業中的行話。還是不明白?徐克導演得《智取威虎山》中就有很典型的DSL使用描述,比如:

土匪:蘑菇,你哪路?什麼價?(什麼人?到哪裡去?)

楊子榮:哈!想啥來啥,想吃奶來了媽媽,想孃家的人,孩子他舅舅來了。(找同行)

楊子榮:拜見三爺!

土匪:天王蓋地虎!(你好大的膽!敢來氣你的祖宗?)

楊子榮:寶塔鎮河妖!(要是那樣,叫我從山上摔死,掉河裡淹死。)

土匪:野雞悶頭鑽,哪能上天王山!(你不是正牌的。)

楊子榮:地上有的是米,喂呀,有根底!(老子是正牌的,老牌的。)

Gradle中也有類似的行話,比如sourceSets代表原始檔的集合等..... 太多了,記不住 。以後我們都會接觸到這些行話。那麼,對使用者而言,這些行話的好處是什麼呢?這就是:

一句行話可以包含很多意思,而且在這個行當裡的人一聽就懂,不用解釋。另外,基於行話,我們甚至可以建立一個模板,使用者只要往這個模板裡填必須要填的內容,Gradle就可以非常漂亮得完成工作,得到想要的東西。

這就和現在的智慧炒菜機器似的,只要選擇菜譜,把食材準備好,剩下的事情就不用你操心了。吃貨們對這種做菜方式肯定是以反感為主,太沒有特色了。但是程式設計師對Gradle類似做法卻熱烈擁抱。

到此,大家應該明白要真正學會Gradle恐怕是離不開下面兩個基礎知識:

  • Groovy,由於它基於Java,所以我們僅介紹Java之外的東西。瞭解Groovy語言是掌握Gradle的基礎。
  • Gradle作為一個工具,它的行話和它“為人處事”的原則。

三、Groovy介紹

Groovy是一種動態語言。這種語言比較有特點,它和Java一樣,也運行於Java虛擬機器中。恩??對頭,簡單粗暴點兒看,你可以認為Groovy擴充套件了Java語言。比如,Groovy對自己的定義就是:Groovy是在 java平臺上的、 具有像Python, Ruby 和 Smalltalk 語言特性的靈活動態語言, Groovy保證了這些特性像 Java語法一樣被 Java開發者使用。

除了語言和Java相通外,Groovy有時候又像一種指令碼語言。前文也提到過,當我執行Groovy指令碼時,Groovy會先將其編譯成Java類位元組碼,然後通過Jvm來執行這個Java類。圖1展示了Java、Groovy和Jvm之間的關係。

實際上,由於Groovy Code在真正執行的時候已經變成了Java位元組碼,所以JVM根本不知道自己執行的是Groovy程式碼 。

下面我們將介紹Groovy。由於此文的主要目的是Gradle,所以我們不會過多討論Groovy中細枝末節的東西,而是把知識點集中在以後和Gradle打交道時一些常用的地方上。

3.1  Groovy開發環境

在學習本節的時候,最好部署一下Groovy開發環境。根據 Groovy官網 的介紹,部署Groovy開發環境非常簡單,在Ubuntu或者cygwin之類的地方:

  • curl -s get.gvmtool.net | bash
  • source "$HOME/.gvm/bin/gvm-init.sh"
  • gvm install groovy
  • 執行完最後一步,Groovy就下載並安裝了。

然後,建立一個test.groovy檔案,裡邊只有一行程式碼:

println  "hello groovy"
  • 執行groovy test.groovy,輸出結果如圖2所示:

親們,必須要完成上面的操作啊。做完後,有什麼感覺和體會?

最大的感覺可能就是groovy和shell指令碼,或者python好類似。

另外,除了可以直接使用JDK之外,Groovy還有一套 GDK 。

說實話,看了這麼多家API文件,還是Google的Android API文件做得好。其頁面中右上角有一個搜尋欄,在裡邊輸入一些關鍵字,瞬間就能列出候選類,相關文件,方便得不得了啊.....

3.2  一些前提知識

為了後面講述方面,這裡先介紹一些前提知識。初期接觸可能有些彆扭,看習慣就好了。

  • Groovy註釋標記和Java一樣,支援 // 或者 /**/
  • Groovy語句可以不用分號結尾。Groovy為了儘量減少程式碼的輸入,確實煞費苦心
  • Groovy中支援動態型別,即 定義變數的時候可以不指定其型別 。Groovy中,變數定義可以使用關鍵字def。 注意,雖然def不是必須的,但是為了程式碼清晰,建議還是使用def關鍵字
   def variable1 = 1   //可以不使用分號結尾
   def varable2 = "I am a person"
   def  int x = 1   //變數定義時,也可以直接指定型別
  •   函式定義時,引數的型別也可以不指定。比如
String testFunction(arg1,arg2){//無需指定引數型別
  ...
}
  • 除了變數定義可以不指定型別外,Groovy中函式的返回值也可以是無型別的。比如:

//無型別的函式定義,必須使用def關鍵字

def  nonReturnTypeFunc(){
     last_line   //最後一行程式碼的執行結果就是本函式的返回值
}

//如果指定了函式返回型別,則可不必加def關鍵字來定義函式
String  getString(){
   return "I am a string"
}

其實,所謂的無返回型別的函式,我估計內部都是按返回Object型別來處理的。畢竟,Groovy是基於Java的,而且最終會轉成Java Code執行在JVM上

  • 函式返回值:Groovy的函式裡,可以不使用return xxx來設定xxx為函式返回值。如果不使用return語句的話,則函式裡最後一句程式碼的執行結果被設定成返回值。比如
//下面這個函式的返回值是字串"getSomething return value"

def getSomething(){

      "getSomething return value" //如果這是最後一行程式碼,則返回型別為String

      1000 //如果這是最後一行程式碼,則返回型別為Integer

}

注意,如果函式定義時候指明瞭返回值型別的話,函式中則必須返回正確的資料型別,否則執行時報錯。如果使用了動態型別的話,你就可以返回任何型別了。

  • Groovy對字串支援相當強大,充分吸收了一些指令碼語言的優點:

1  單引號''中的內容嚴格對應Java中的String,不對$符號進行轉義

   def singleQuote='I am $ dolloar'  //輸出就是I am $ dolloar

2  雙引號""的內容則和指令碼語言的處理有點像,如果字元中有$號的話,則它會 $表示式 先求值。

   def doubleQuoteWithoutDollar = "I am one dollar" //輸出 I am one dollar
   def x = 1
   def doubleQuoteWithDollar = "I am $x dolloar" //輸出I am 1 dolloar

3 三個引號'''xxx'''中的字串支援隨意換行 比如

def multieLines = ''' begin
     line  1 
     line  2
     end '''
  • 最後,除了每行程式碼不用加分號外,Groovy中函式呼叫的時候還可以不加括號。比如:
println("test") ---> println "test"

注意,雖然寫程式碼的時候,對於函式呼叫可以不帶括號,但是Groovy經常把屬性和函式呼叫混淆。比如

def getSomething(){
   "hello"
}

getSomething()   //如果不加括號的話,Groovy會誤認為getSomething是一個變數。

所以,呼叫函式要不要帶括號,我個人意見是如果這個函式是Groovy API或者Gradle API中比較常用的,比如println,就可以不帶括號。否則還是帶括號。Groovy自己也沒有太好的辦法解決這個問題,只能 兵來將擋水來土掩 了。

好了,瞭解上面一些基礎知識後,我們再介紹點深入的內容。

3.3  Groovy中的資料型別

Groovy中的資料型別我們就介紹兩種和Java不太一樣的:

  • 一個是Java中的基本資料型別。
  • 另外一個是Groovy中的容器類。
  • 最後一個非常重要的是閉包。

放心,這裡介紹的東西都很簡單

3.3.1  基本資料型別

作為動態語言,Groovy世界中的所有事物都是物件。所以, int,boolean這些Java中的基本資料型別,在Groovy程式碼中其實對應的是它們的包裝資料型別。比如int對應為Integer,boolean對應為Boolean。 比如下圖中的程式碼執行結果:

圖4  int實際上是Integer

3.3.2  容器類

Groovy中的容器類很簡單,就三種:

  • List:連結串列,其底層對應Java中的List介面,一般用ArrayList作為真正的實現類。
  • Map:鍵-值表,其底層對應Java中的LinkedHashMap。
  • Range:範圍,它其實是List的一種拓展。

對容器而言,我們最重要的是瞭解它們的用法。下面是一些簡單的例子:

1.  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

2.  Map類

容器變數定義

變數定義:Map變數由[:]定義,比如

def aMap = ['key1':'value1','key2':true] 

Map由[:]定義,注意其中的冒號。冒號左邊是key,右邊是Value。key必須是字串,value可以是任何物件。另外,key可以用''或""包起來,也可以不用引號包起來。比如

def aNewMap = [key1:"value",key2:true] //其中的key1和key2預設被
處理成字串"key1"和"key2"

不過Key要是不使用引號包起來的話,也會帶來一定混淆,比如

def key1="wowo"
def aConfusedMap=[key1:"who am i?"]

aConfuseMap中的key1到底是"key1"還是變數key1的值“wowo”?顯然,答案是字串"key1"。如果要是"wowo"的話,則aConfusedMap的定義必須設定成:

def aConfusedMap=[(key1):"who am i?"]

Map中元素的存取更加方便,它支援多種方法:

println aMap.keyName    <==這種表達方法好像key就是aMap的一個成員變數一樣
println aMap['keyName'] <==這種表達方法更傳統一點
aMap.anotherkey = "i am map"  <==為map新增新元素

3.  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

3.3.4  Groovy API的一些祕笈

前面講這些東西,主要是讓大家瞭解Groovy的語法。實際上在coding的時候,是離不開SDK的。由於Groovy是動態語言,所以要使用它的SDK也需要掌握一些小訣竅。

Groovy的API文件位於 http://www.groovy-lang.org/api.html

以上文介紹的Range為例,我們該如何更好得使用它呢?

  • 先定位到Range類。它位於groovy.lang包中:

有了API文件,你就可以放心呼叫其中的函數了。 不過,不過,不過 :我們剛才程式碼中用到了Range.from/to屬性值,但翻看Range API文件的時候,其實並沒有這兩個成員變數。圖6是Range的方法

文件中並沒有說明Range有from和to這兩個屬性,但是卻有getFrom和getTo這兩個函式。What happened? 原來:

根據Groovy的原則,如果一個類中有名為xxyyzz這樣的屬性(其實就是成員變數),Groovy會自動為它新增getXxyyzz和setXxyyzz兩個函式,用於獲取和設定xxyyzz屬性值。

注意,get和set後第一個字母是大寫的

所以,當你看到Range中有getFrom和getTo這兩個函式時候,就得知道潛規則下,Range有from和to這兩個屬性。當然,由於它們不可以被外界設定,所以沒有公開setFrom和setTo函式。

3.4  閉包

3.4.1  閉包的樣子

閉包,英文叫Closure,是Groovy中非常重要的一個數據型別或者說一種概念了。閉包的歷史來源,種種好處我就不說了。我們直接看怎麼使用它!

閉包,是一種資料型別,它代表了一段可執行的程式碼。其外形如下:

def aClosure = {//閉包是一段程式碼,所以需要用花括號括起來..  
    Stringparam1, int param2 ->  //這個箭頭很關鍵。箭頭前面是引數定義,箭頭後面是程式碼  
    println"this is code" //這是程式碼,最後一句是返回值,  
   //也可以使用return,和Groovy中普通函式一樣  
}

簡而言之,Closure的定義格式是:

def xxx = {paramters -> code}  //或者  
def xxx = {無引數,純code}  這種case不需要->符號

說實話,從C/C++語言的角度看,閉包和函式指標很像 。閉包定義好後,要呼叫它的方法就是:

閉包物件.call(引數)  或者更像函式指標呼叫的方法:

閉包物件(引數)

比如:

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

上面就是一個閉包的定義和使用。在閉包中,還需要注意一點:

如果閉包沒定義引數的話,則隱含有一個引數,這個引數名字叫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")  <==報錯喔!

3.4.2  Closure使用中的注意點

1.  省略圓括號

閉包在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
}

上面程式碼有兩個知識點:

  • each函式呼叫的圓括號不見了 !原來,Groovy中,當函式的最後一個引數是閉包的話,可以省略圓括號。比如
def  testClosure(int a1,String b1, Closure closure){
      //do something
      closure() //呼叫閉包
}
那麼呼叫的時候,就可以免括號!
testClosure (4, "test", {
   println "i am in closure"
} )  //紅色的括號可以不寫..

注意,這個特點非常關鍵,因為以後在Gradle中經常會出現圖7這樣的程式碼:

經常碰見圖7這樣的沒有圓括號的程式碼。省略圓括號雖然使得程式碼簡潔,看起來更像指令碼語言,但是它這經常會讓我confuse(不知道其他人是否有同感),以doLast為例,完整的程式碼應該按下面這種寫法:

doLast({
   println 'Hello world!'
})

有了圓括號,你會知道 doLast只是把一個Closure物件傳了進去。很明顯,它不代表這段指令碼解析到doLast的時候就會呼叫println 'Hello world!' 。

但是把圓括號去掉後,就感覺好像println 'Hello world!'立即就會被呼叫一樣!

2.  如何確定Closure的引數

另外一個比較讓人頭疼的地方是,Closure的引數該怎麼搞?還是剛才的each函式:

public static <T> List<T> each(List<T> self, Closure closure)

如何使用它呢?比如:

def iamList = [1,2,3,4,5]  //定義一個List變數
iamList.each{  //呼叫它的each函式,只要傳入一個Closure就可以了。
  println it
}

看起來很輕鬆,其實:

  • 對於each所需要的Closure,它的引數是什麼?有多少個引數?返回值是什麼?

我們能寫成下面這樣嗎?

iamList.each{String name,int x ->
  return x
}  //執行的時候肯定報錯!

所以,Closure雖然很方便,但是它一定會和使用它的上下文有極強的關聯。要不,作為類似回撥這樣的東西,我如何知道呼叫者傳遞什麼引數給Closure呢?

此問題如何破解?只能通過查詢API文件才能瞭解上下文語義。比如下圖8:

圖8中:

  • each函式說明中,將給指定的closure傳遞Set中的每一個item。所以,closure的引數只有一個。
  • findAll中, 絕對抓瞎 了。一個是沒說明往Closure裡傳什麼。另外沒說明Closure的返回值是什麼.....。

對Map的findAll而言,Closure可以有兩個引數。findAll會將Key和Value分別傳進去。並且,Closure返回true,表示該元素是自己想要的。返回false表示該元素不是自己要找的 。示意程式碼如圖9所示:

Closure的使用有點坑,很大程度上依賴於你對API的熟悉程度,所以最初階段,SDK查詢是少不了的。

3.5  指令碼類、檔案I/O和XML操作

最後,我們來看一下Groovy中比較高階的用法。

3.5.1  指令碼類

1.  指令碼中import其他類

Groovy中可以像Java那樣寫package,然後寫類。比如在資料夾com/cmbc/groovy/目錄中放一個檔案,叫Test.groovy,如圖10所示:

你看,圖10中的Test.groovy和Java類就很相似了。當然,如果不宣告public/private等訪問許可權的話,Groovy中類及其變數預設都是public的。

現在,我們在測試的根目錄下建立一個test.groovy檔案。其程式碼如下所示:

你看,test.groovy先import了com.cmbc.groovy.Test類,然後建立了一個Test型別的物件,接著呼叫它的print函式。

這兩個groovy檔案的目錄結構如圖12所示:

在groovy中,系統自帶會載入當前目錄/子目錄下的xxx.groovy檔案。所以,當執行groovy test.groovy的時候,test.groovy import的Test類能被自動搜尋並載入到。

2.  指令碼到底是什麼

Java中,我們最熟悉的是類。但是我們在Java的一個原始碼檔案中,不能不寫class(interface或者其他....),而Groovy可以像寫指令碼一樣,把要做的事情都寫在xxx.groovy中,而且可以通過groovy xxx.groovy直接執行這個指令碼。這到底是怎麼搞的?

既然是基於Java的,Groovy會先把xxx.groovy中的內容轉換成一個Java類。比如:

test.groovy的程式碼是:

println 'Groovy world!'

Groovy把它轉換成這樣的Java類:

執行 groovyc -d classes test.groovy

groovyc 是groovy的編譯命令,-d classes用於將編譯得到的class檔案拷貝到classes資料夾下

圖13是test.groovy指令碼轉換得到的java class。用jd-gui反編譯它的程式碼:

圖13中:

  • test.groovy被轉換成了一個test類,它從script派生。
  • 每一個指令碼都會生成一個static main函式。這樣,當我們groovy test.groovy的時候,其實就是用java去執行這個main函式
  • 指令碼中的所有程式碼都會放到run函式中 。比如,println 'Groovy world',這句程式碼實際上是包含在run函式裡的。
  • 如果指令碼中定義了函式,則函式會被定義在test類中。

groovyc 是一個比較好的命令,讀者要掌握它的用法。然後利用jd-gui來檢視對應class的Java原始碼。

3.  指令碼中的變數和作用域

前面說了,xxx.groovy只要不是和Java那樣的class,那麼它就是一個指令碼。而且指令碼的程式碼其實都會被放到run函式中去執行。那麼,在Groovy的指令碼中,很重要的一點就是指令碼中定義的 變數和它的作用域 。舉例:

def x = 1 <==注意,這個x有def(或者指明型別,比如 int x = 1)  
def printx(){  
   println x  
}

printx()  <==報錯,說x找不到

為什麼?繼續來看反編譯後的class檔案。

圖14中:

  • printx被定義成test類的成員函式
  • def x = 1 ,這句話是在run中建立的。所以,x=1從程式碼上看好像是在整個指令碼中定義的,但實際上printx訪問不了它。printx是test成員函式,除非x也被定義成test的成員函式,否則printx不能訪問它。

那麼,如何使得printx能訪問x呢?很簡單,定義的時候不要加型別和def。即:

x = 1  <==注意,去掉def或者型別
def printx(){
   println x
}
printx()  <==OK

這次Java原始碼又變成什麼樣了呢?

圖15中,x也沒有被定義成test的成員函式,而是在run的執行過程中,將x作為一個屬性新增到test例項物件中了。然後在printx中,先獲取這個屬性。

注意,Groovy的文件說 x = 1這種定義將使得x變成test的成員變數,但從反編譯情況看,這是不對的.....

雖然printx可以訪問x變量了,但是假如有其他指令碼卻無法訪問x變數。因為它不是test的成員變數。

比如,我在測試目錄下建立一個新的名為test1.groovy。這個test1將訪問test.groovy中定義的printx函式:

這種方法使得我們可以將程式碼分成模組來編寫, 比如將公共的功能放到test.groovy中,然後使用公共功能的程式碼放到test1.groovy中 。

執行groovy test1.groovy,報錯。說x找不到。這是因為x是在test的run函式動態加進去的。怎麼辦?

import groovy.transform.Field;   //必須要先import
@Field x = 1  <==在x前面加上@Field標註,這樣,x就徹徹底底是test的成員變量了。

檢視編譯後的test.class檔案,得到:

這個時候,test.groovy中的x就成了test類的成員函數了。如此,我們可以在script中定義那些需要輸出給外部指令碼或類使用的變量了!

3.5.2  檔案I/O操作

本節介紹下Groovy的檔案I/O操作。直接來看例子吧,雖然比Java看起來簡單,但要理解起來其實比較難。尤其是當你要自己查SDK並編寫程式碼的時候。

整體說來,Groovy的I/O操作是在原有Java I/O操作上進行了更為簡單方便的封裝,並且使用Closure來簡化程式碼編寫。主要封裝瞭如下一些了類:

1.  讀檔案

Groovy中,檔案讀操作簡單到令人髮指:

def targetFile = new File(檔名)  <==File物件還是要建立的。

然後開啟http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/File.html

看看Groovy定義的API:

1 讀該檔案中的每一行:eachLine的唯一引數是一個Closure。Closure的引數是檔案每一行的內容

其內部實現肯定是Groovy開啟這個檔案,然後讀取檔案