1. 程式人生 > >Groovy指令碼基礎全攻略

Groovy指令碼基礎全攻略

1 背景

Groovy指令碼基於Java且拓展了Java,所以從某種程度來說掌握Java是學習Groovy的前提,故本文適用於不熟悉Groovy卻想快速得到Groovy核心基礎乾貨的Java開發者(注意是Java),因為我的目的不是深入學習Groovy語言,所以本文基本都是靠程式碼來解釋,這樣最直觀,同時也夠乾貨基礎入門Groovy的特點和結構。

這裡寫圖片描述

開始介紹前先給一個大法,《官方權威指南》英文好的可以直接略過本文後續內容,我需要的只是Groovy皮毛;再次向Groovy的標誌致敬,左手一個Java,右手一個Groovy,不好意思,我技術水平太Low了(T–T__《琅琊榜》看多了!!!)。

這裡寫圖片描述

Groovy是一種動態語言,它和Java類似(算是Java的升級版,但是又具備指令碼語言的特點),都在Java虛擬機器中執行。當執行Groovy指令碼時它會先被編譯成Java類位元組碼,然後通過JVM虛擬機器執行這個Java位元組碼類。

快速安裝指南:

安裝Groovy在各種Bash下都是通用的,具體如下命令就可搞定:

$ curl -s get.sdkman.io | bash
$ source "$HOME/.sdkman/bin/sdkman-init.sh"
$ sdk install groovy
$ groovy -version
//至此就可以享用了!

我們在寫Groovy程式碼時可以直接使用自己喜歡的文字編輯器編輯OK以後以.groovy字尾儲存,然後在終端執行如下命令即可執行:

$ groovy ./TestFile.groovy

或者我們可以通過groovyConsole來進行groovy程式碼開發執行(由於不需要特別深入學習使用Groovy,所以個人非常喜歡這種模式的開發執行),如下圖:

這裡寫圖片描述

再或者我們還可以使用Intellij IDEA等工具安裝groovy外掛進行groovy開發,這裡不再一一敘述了(配置環境點我),直接給出一個讀取指定檔案內容列印的例子,如下:

這裡寫圖片描述

OK,有了上面這些簡單粗暴的基礎和環境之後那我們快速開戰吧。

2 語法基礎

這裡開始我們就來快速簡單粗暴的瞭解一下Groovy語法,其實和Java類似,但也有些區別,下面我們一步一步來看吧,切記對比學習,這才是祕笈。

2-1 註釋

Groovy的單行註釋、多行註釋、文件註釋基本都和Java一樣,沒啥特殊的,不再細說。只有一種特殊的單行註釋需要留意一下即可。如下:

#!/usr/bin/env groovy
println "Hello from the shebang line"

這種註釋通常是用來給UNIX系統宣告允許指令碼執行的型別的,一般都是固定寫法,沒啥講究的。

2-2 關鍵字

Groovy有如下一些關鍵字,我們些程式碼命名時要注意:

as、assert、break、case、catch、class、const、continue、def、default、do、else、enum、extends、false、finally、for、goto、if、implements、import、in、instanceof、interface、new、null、package、return、super、switch、this、throw、throws、trait、true、try、while

這玩意和其他語言一樣,沒啥特殊的,自行腦補。

2-3 識別符號

對於Groovy的標示符和Java還是有些共同點和區別的,特別是引用標示符的區別,具體可以往下看。

2-3-1 普通識別符號

普通識別符號定義和C語言類似,只能以字母、美元符、下劃線開始,不能以數字開頭。如下例子:

//正確
def name
def $name
def name_type
def foo.assert
//錯誤
def 5type
def a+b

2-3-2 引用識別符號

引用識別符號出現在點後的表示式中,我們可以如下一樣使用:

def map = [:]
//引用標示符中出現空格也是對的
map."an identifier with a space and double quotes" = "ALLOWED"
//引用標示符中出現橫線也是對的
map.'with-dash-signs-and-single-quotes' = "ALLOWED"

assert map."an identifier with a space and double quotes" == "ALLOWED"
assert map.'with-dash-signs-and-single-quotes' == "ALLOWED"

當然了,Groovy的所有字串都可以當作引用標示符定義,如下:

//如下型別字串作為引用識別符號都是對的
map.'single quote'
map."double quote"
map.'''triple single quote'''
map."""triple double quote"""
map./slashy string/
map.$/dollar slashy string/$

//稍微特殊的GString,也是對的
def firstname = "Homer"
map."Simson-${firstname}" = "Homer Simson"

assert map.'Simson-Homer' == "Homer Simson"

2-4 字元及字串

Groovy有java.lang.String和groovy.lang.GString兩中字串物件型別,具體如下細說。

2-4-1 單引號字串

單引號字串是java.lang.String型別的,不支援站位符插值操作,譬如:

def name = 'Test Groovy!'
def body = 'Test $name'

assert name == 'Test Groovy!'
assert body == 'Test $name'		//不會替換$name站位符

Groovy的字串可以通過”+“直接拼接,譬如:

assert 'ab' == 'a' + 'b'

其中涉及轉義字元規則同Java,只用特殊注意”’“的轉義即可。

2-4-2 三重單引號字串

三重單引號字串是java.lang.String型別的,不支援站位符插值操作,可以標示多行字串,譬如:

def aMultilineString = '''line one
line two
line three'''

三重單引號字串允許字串的內容在多行出現,新的行被轉換為“\n”,其他所有的空白字元都被完整的按照文字原樣保留;字元開頭新增“/”表示字元內容不轉義反斜槓“\”,只有在反斜槓接下來是一個字元u的時候才需要進行轉義,因為\u表示一個unicode轉義。如下:

def strippedFirstNewline = '''\
line one
line two
line three
'''

assert !strippedFirstNewline.startsWith('\n')

2-4-3 雙引號字串

雙引號字串支援站位插值操作,如果雙引號字串中不包含站位符則是java.lang.String型別的,如果雙引號字串中包含站位符則是groovy.lang.GString型別的。

對於插值佔位符我們可以用${}或者$來標示,${}用於一般替代字串或者表示式,$主要用於A.B的形式中,具體如下例子:

def name = 'Guillaume' // a plain string
def greeting = "Hello ${name}"
assert greeting.toString() == 'Hello Guillaume'

def sum = "The sum of 2 and 3 equals ${2 + 3}"
assert sum.toString() == 'The sum of 2 and 3 equals 5'

def person = [name: 'Guillaume', age: 36]
assert "$person.name is $person.age years old" == 'Guillaume is 36 years old'

特別注意,$只對A.B等有效,如果表示式包含括號(像方法呼叫)、大括號、閉包等符號則是無效的。譬如:

def number = 3.14
shouldFail(MissingPropertyException) {
    println "$number.toString()"
}

//該程式碼執行丟擲groovy.lang.MissingPropertyException異常,因為Groovy認為去尋找number的名為toString的屬性,所以異常

注意,在表示式中訪問屬性前必須保證屬性已經定義好(值為空也可以),如果使用了未定義的屬性會丟擲groovy.lang.MissingPropertyException異常。 GString還支援延遲運算,譬如在GString中使用閉包,閉包在呼叫GString的toString()方法時被延遲執行;閉包中可以有0或1個引數,若指定一個引數,則引數會被傳入一個Writer物件,我們可以利用這個Writer物件來寫入字元,若沒有引數,閉包返回值的toString()方法被呼叫。譬如:

//無引數閉包
def sParameterLessClosure = "1 + 2 == ${-> 3}" 
assert sParameterLessClosure == '1 + 2 == 3'
//一個引數閉包
def sOneParamClosure = "1 + 2 == ${ w -> w << 3}" 
assert sOneParamClosure == '1 + 2 == 3'

上面瞭解了GString的推遲運算特性,下面我們再來看一個牛逼的特性,如下:

def number = 1 
def eagerGString = "value == ${number}"
def lazyGString = "value == ${ -> number }"

assert eagerGString == "value == 1" 
assert lazyGString ==  "value == 1" 

number = 2 
assert eagerGString == "value == 1" 
assert lazyGString ==  "value == 2" 

可以看見,eagerGString是普通的雙引號插值站位替換,lazyGString是雙引號閉包插值替換,我們可以發現在number變為2以後他們的運算結果就有了差異。可以明顯推理到結論,一個普通插值表示式值替換實際是在GString建立的時刻,一個包含閉包的表示式由於延遲運算調運toString()方法,所以會產生一個新的字串值。

當然了,GString和String即使字串一樣他們的HashCode也不會一樣,譬如:

assert "one: ${1}".hashCode() != "one: 1".hashCode()

由於相同字串的String與GString的HashCode不同,所以我們一定要避免使用GString作為MAP的key,譬如:

def key = "a"
def m = ["${key}": "letter ${key}"]     

assert m["a"] == null   //由於key的HashCode不同,所以取不到

其中涉及轉義字元規則同Java,只用特殊注意””“的轉義即可。

2-4-4 多重雙引號字串

多重雙引號字串也支援站位插值操作,我們要特別注意在多重雙引號字串中的單引號和雙引號轉換問題。譬如:

def name = 'Groovy'
def template = """
    Dear Mr ${name},

    You're the winner of the lottery!

    Yours sincerly,

    Dave
"""

assert template.toString().contains('Groovy')

2-4-5 斜線字串

斜線字串其實和雙引號字串很類似,通常用在正則表示式中,下面我們看幾個例子,如下:

//普通使用
def fooPattern = /.*foo.*/
assert fooPattern == '.*foo.*'
//含轉義字元使用
def escapeSlash = /The character \/ is a forward slash/
assert escapeSlash == 'The character / is a forward slash'
//多行支援
def multilineSlashy = /one
    two
    three/

assert multilineSlashy.contains('\n')
//含站位符使用支援
def color = 'blue'
def interpolatedSlashy = /a ${color} car/

assert interpolatedSlashy == 'a blue car'

特別注意,一個空的斜線字串會被Groovy解析器解析為一註釋。

2-4-6 字元Characters

不像Java,Groovy沒有明確的Characters。但是我們可以有如下三種不同的方式來將字串作為字元處理,譬如:

char c1 = 'A' 
assert c1 instanceof Character

def c2 = 'B' as char 
assert c2 instanceof Character

def c3 = (char)'C' 
assert c3 instanceof Character

2-5 數字Numbers

Groovy支援各種型別的整型和數值型別,通常支援Java支援的那些,下面我們仔細來說說。

2-5-1 整型

Groovy像Java一樣支援如下一些整型,byte、char、short、int、long、java.lang.BigInteger。我們在使用中可以像下面例子一樣:

// primitive types
byte  b = 1
char  c = 2
short s = 3
int   i = 4
long  l = 5

// infinite precision
BigInteger bi =  6


int xInt = 077
assert xInt == 63

int xInt = 0x77
assert xInt == 119

int xInt = 0b10101111
assert xInt == 175

2-5-2 浮點型

Groovy像Java一樣支援如下一些浮點型,float、double、java.lang.BigDecimal。我們在使用中可以像下面例子一樣:

// primitive types
float  f = 1.234
double d = 2.345

// infinite precision
BigDecimal bd =  3.456


assert 1e3  ==  1_000.0
assert 2E4  == 20_000.0
assert 3e+1 ==     30.0
assert 4E-2 ==      0.04

2-6 Booleans型別

Boolean型別沒啥解釋的,和其他語言一樣,就兩個值,如下:

def myBooleanVariable = true
boolean untypedBooleanVar = false
booleanField = true

比較簡單,沒啥特例,自行腦補。

2-7 Lists型別

Groovy同樣支援java.util.List型別,在Groovy中同樣允許向列表中增加或者刪除物件,允許在執行時改變列表的大小,儲存在列表中的物件不受型別的限制;此外還可以通過超出列表範圍的數來索引列表。如下例子:

//使用動態List
def numbers = [1, 2, 3]         
assert numbers instanceof List  
assert numbers.size() == 3

//List中儲存任意型別
def heterogeneous = [1, "a", true]

//判斷List預設型別
def arrayList = [1, 2, 3]
assert arrayList instanceof java.util.ArrayList

//使用as強轉型別
def linkedList = [2, 3, 4] as LinkedList    
assert linkedList instanceof java.util.LinkedList

//定義指定型別List
LinkedList otherLinked = [3, 4, 5]          
assert otherLinked instanceof java.util.LinkedList

//定義List使用
def letters = ['a', 'b', 'c', 'd']
//判斷item值
assert letters[0] == 'a'     
assert letters[1] == 'b'
//負數下標則從右向左index
assert letters[-1] == 'd'    
assert letters[-2] == 'c'
//指定item賦值判斷
letters[2] = 'C'             
assert letters[2] == 'C'
//給List追加item
letters << 'e'               
assert letters[ 4] == 'e'
assert letters[-1] == 'e'
//獲取一段List子集
assert letters[1, 3] == ['b', 'd']         
assert letters[2..4] == ['C', 'd', 'e'] 

//多維List支援
def multi = [[0, 1], [2, 3]]     
assert multi[1][0] == 2 

2-8 Arrays型別

Groovy中陣列和Java類似,具體如下:

//定義初始化String陣列
String[] arrStr = ['Ananas', 'Banana', 'Kiwi']  
assert arrStr instanceof String[]    
assert !(arrStr instanceof List)

//使用def定義初始化int陣列
def numArr = [1, 2, 3] as int[]      
assert numArr instanceof int[]       
assert numArr.size() == 3

//宣告定義多維陣列指明寬度
def matrix3 = new Integer[3][3]         
assert matrix3.size() == 3

//宣告多維陣列不指定寬度
Integer[][] matrix2                     
matrix2 = [[1, 2], [3, 4]]
assert matrix2 instanceof Integer[][]

//陣列的元素使用及賦值操作
String[] names = ['Cédric', 'Guillaume', 'Jochen', 'Paul']
assert names[0] == 'Cédric'     
names[2] = 'Blackdrag'          
assert names[2] == 'Blackdrag'

2-9 Maps型別

Map是“鍵-值”對的集合,在Groovy中鍵key不一定是String,可以是任何物件(實際上Groovy中的Map就是java.util.Linke dHashMap)。如下:

//定義一個Map
def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF']   
//獲取一些指定key的value進行判斷操作
assert colors['red'] == '#FF0000'    
assert colors.green  == '#00FF00'
//給指定key的對賦值value操作與判斷    
colors['pink'] = '#FF00FF'           
colors.yellow  = '#FFFF00'           
assert colors.pink == '#FF00FF'
assert colors['yellow'] == '#FFFF00'
//判斷Map的型別
assert colors instanceof java.util.LinkedHashMap
//訪問Map中不存在的key為null
assert colors.unknown == null

//定義key型別為數字的Map
def numbers = [1: 'one', 2: 'two']
assert numbers[1] == 'one'

對於Map需要特別注意一種情況,如下:

//把一個定義的變數作為Map的key,訪問Map的該key是失敗的
def key = 'name'
def person = [key: 'Guillaume']      
assert !person.containsKey('name')   
assert person.containsKey('key') 

//把一個定義的變數作為Map的key的正確寫法---新增括弧,訪問Map的該key是成功的
person = [(key): 'Guillaume']        
assert person.containsKey('name')    
assert !person.containsKey('key') 

3 運算子

關於Groovy的運算子介紹類似於上面一樣,我們重點突出與Java的不同點,相同點自行腦補。

Groovy支援**次方運算子,如下:

assert  2 ** 3 == 8

def f = 3
f **= 2
assert f == 9

Groovy非運算子如下:

assert (!true)    == false                      
assert (!'foo')   == false                      
assert (!'')      == true 

Groovy支援?.安全佔位符,這個運算子主要用於避免空指標異常,譬如:

def person = Person.find { it.id == 123 }    
def name = person?.name                      
assert name == null  

Groovy支援[email protected]直接域訪問操作符,因為Groovy自動支援屬性getter方法,但有時候我們有一個自己寫的特殊getter方法,當不想呼叫這個特殊的getter方法則可以用直接域訪問操作符。如下:

class User {
    public final String name                 
    User(String name) { this.name = name}
    String getName() { "Name: $name" }       
}
def user = new User('Bob')

assert user.name == 'Name: Bob'
assert user.@name == 'Bob'  

Groovy支援.&方法指標操作符,因為閉包可以被作為一個方法的引數,如果想讓一個方法作為另一個方法的引數則可以將一個方法當成一個閉包作為另一個方法的引數。如下:

    def list = ['a','b','c']  
    //常規寫法 
    list.each{  
        println it  
    }  

    String printName(name){  
        println name  
    }  

    //方法指標操作符寫法
    list.each(this.&printName)  

Groovy支援將?:三目運算子簡化為二目,如下:

displayName = user.name ? user.name : 'Anonymous'   
displayName = user.name ?: 'Anonymous' 

Groovy支援*.展開運算子,一個集合使用展開運算子可以得到一個元素為原集合各個元素執行後面指定方法所得值的集合,如下:

cars = [
   new Car(make: 'Peugeot', model: '508'),
   null,                                              
   new Car(make: 'Renault', model: 'Clio')]
assert cars*.make == ['Peugeot', null, 'Renault']     
assert null*.make == null 

關於Groovy的其他運算子就不多說,類比Java吧。

4 程式結構

這裡主要討論Groovy的程式碼組成結構,具體如下細則。

4-1 包名

包名的定義和作用及含義完全和Java一樣,不再介紹,如下:

// defining a package named com.yoursite
package com.yoursite

4-2 Imports引入

常規的imports導包操作和Java一樣,如下:

//例1:
import groovy.xml.MarkupBuilder

// using the imported class to create an object
def xml = new MarkupBuilder()
assert xml != null

//例2:
import groovy.xml.*

def markupBuilder = new MarkupBuilder()
assert markupBuilder != null
assert new StreamingMarkupBuilder() != null

//例3:
import static Boolean.FALSE

assert !FALSE

//例4:特殊的,相當於用as取別名
import static Calendar.getInstance as now

assert now().class == Calendar.getInstance().class

不過要特別注意,Groovy與Java類似,已經幫我們預設匯入了一些常用的包,所以在我們使用這些包的類時就不用再像上面那樣匯入了,如下是自動匯入的包列表:

import java.lang.*
import java.util.*
import java.io.*
import java.net.*
import groovy.lang.*
import groovy.util.*
import java.math.BigInteger
import java.math.BigDecimal

4-3 指令碼與類(指令碼的實質)

相對於傳統的Java類,一個包含main方法的Groovy類可以如下書寫:

class Main {                                    
    static void main(String... args) {          
        println 'Groovy world!'                 
    }
}

和Java一樣,程式會從這個類的main方法開始執行,這是Groovy程式碼的一種寫法,實際上執行Groovy程式碼完全可以不需要類或main方法,所以更簡單的寫法如下:

println 'Groovy world!'

上面這兩中寫法其實是一樣的,具體我們可以通過如下命令進行編譯為class檔案:

groovyc demo.groovy //編譯Groovy原始碼為class

我們使用反編譯工具可以檢視到這個demo.groovy類原始碼如下:

import org.codehaus.groovy.runtime.InvokerHelper
class Main extends Script {                     
    def run() {                                 
        println 'Groovy world!'                 
    }
    static void main(String[] args) {           
        InvokerHelper.runScript(Main, args)     
    }
}

可以看見,上面我們寫的groovy檔案編譯後的class其實是Java類,該類從Script類派生而來(查閱API);可以發現,每個指令碼都會生成一個static main方法,我們執行groovy指令碼的實質其實是執行的這個Java類的main方法,指令碼原始碼裡所有程式碼都被放到了run方法中,指令碼中定義的方法(該例暫無)都會被定義在Main類中。

通過上面可以發現,Groovy的實質就是Java的class,也就是說他一定會和Java一樣存在變數作用域!對哦,前面我們解釋變數時竟然沒說到這個東東,這裡說下吧。看下面例子:

//單個Groovy原始碼檔案,執行會報錯找不到num變數
def num = 1 
def printNum(){  
    println num  
}

//單個Groovy原始碼檔案,執行會報錯找不到num變數
int num = 1 
def printNum(){  
    println num  
}  

//單個Groovy原始碼檔案,執行OK成功
num = 1 
def printNum(){  
    println num  
}  

上面的例子可以發現,我們如果想要在Groovy的方法中使用Groovy的變數則不能有修飾符。然而,如果我們想在B.groovy檔案訪問A.groovy檔案的num變數咋辦呢,我們可以使用Field註解,具體操作如下:

import groovy.transform.Field;
@Field num = 1

哈哈,這就是Groovy的變數作用域了,如果你想知道上面這些寫法為啥出錯,很簡單,自己動手整成Java原始碼相信你一定可以看懂為啥鳥。

5 閉包

Groovy的閉包(closure)是一個非常重要的概念,閉包是可以用作方法引數的程式碼塊,Groovy的閉包更象是一個程式碼塊或者方法指標,程式碼在某處被定義然後在其後的呼叫處執行。

5-1 語法

定義一個閉包:

{ [closureParameters -> ] statements }

//[closureparameters -> ]是可選的逗號分隔的引數列表,引數類似於方法的引數列表,這些引數可以是型別化或非型別化的。

如下給出幾個有效的閉包定義例子:

//最基本的閉包
{ item++ }                                          
//使用->將引數與程式碼分離
{ -> item++ }                                       
//使用隱含引數it(後面有介紹)
{ println it }                                      
//使用明確的引數it替代
{ it -> println it }                                
//使用顯示的名為引數
{ name -> println name }                            
//接受兩個引數的閉包
{ String x, int y ->                                
    println "hey ${x} the value is ${y}"
}
//包含一個引數多個語句的閉包
{ reader ->                                         
    def line = reader.readLine()
    line.trim()
}

閉包物件:

一個閉包其實就是一個groovy.lang.Closure型別的例項,如下:

//定義一個Closure型別的閉包
def listener = { e -> println "Clicked on $e.source" }      
assert listener instanceof Closure
//定義直接指定為Closure型別的閉包
Closure callback = { println 'Done!' }                      
Closure<Boolean> isTextFile = {
    File it -> it.name.endsWith('.txt')                     
}

調運閉包:

其實閉包和C語言的函式指標非常像,我們定義好閉包後呼叫的方法有如下兩種形式:

  • 閉包物件.call(引數)

  • 閉包物件(引數)

如下給出例子:

def code = { 123 }
assert code() == 123
assert code.call() == 123

def isOdd = { int i-> i%2 == 1 }                            
assert isOdd(3) == true                                     
assert isOdd.call(2) == false

特別注意,如果閉包沒定義引數則預設隱含一個名為it的引數,如下例子:

def isEven = { it%2 == 0 }                                  
assert isEven(3) == false                                   
assert isEven.call(2) == true 

5-2 引數

普通引數:

一個閉包的普通引數定義必須遵循如下一些原則:

  • 引數型別可選
  • 引數名字
  • 可選的引數預設值
  • 引數必須用逗號分隔

如下是一些例子:

def closureWithOneArg = { str -> str.toUpperCase() }
assert closureWithOneArg('groovy') == 'GROOVY'

def closureWithOneArgAndExplicitType = { String str -> str.toUpperCase() }
assert closureWithOneArgAndExplicitType('groovy') == 'GROOVY'

def closureWithTwoArgs = { a,b -> a+b }
assert closureWithTwoArgs(1,2) == 3

def closureWithTwoArgsAndExplicitTypes = { int a, int b -> a+b }
assert closureWithTwoArgsAndExplicitTypes(1,2) == 3

def closureWithTwoArgsAndOptionalTypes = { a, int b -> a+b }
assert closureWithTwoArgsAndOptionalTypes(1,2) == 3

def closureWithTwoArgAndDefaultValue = { int a, int b=2 -> a+b }
assert closureWithTwoArgAndDefaultValue(1) == 3

隱含引數:

當一個閉包沒有顯式定義一個引數列表時,閉包總是有一個隱式的it引數。如下:

def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'

上面的類似下面這個例子:

def greeting = { it -> "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'

當然啦,如果你想宣告一個不接受任何引數的閉包,且必須限定為沒有引數的呼叫,那麼你必須將它宣告為一個空的引數列表,如下:

def magicNumber = { -> 42 }
// this call will fail because the closure doesn't accept any argument
magicNumber(11)

可變長引數:

Groovy的閉包支援最後一個引數為不定長可變長度的引數,具體用法如下:

def concat1 = { String... args -> args.join('') }           
assert concat1('abc','def') == 'abcdef'                     
def concat2 = { String[] args -> args.join('') }            
assert concat2('abc', 'def') == 'abcdef'

def multiConcat = { int n, String... args ->                
    args.join('')*n
}
assert multiConcat(2, 'abc','def') == 'abcdefabcdef'

5-3 閉包省略調運

很多方法的最後一個引數都是一個閉包,我們可以在這樣的方法調運時進行略寫括弧。比如:

def debugClosure(int num, String str, Closure closure){  
      //dosomething  
}  

debugClosure(1, "groovy", {  
   println"hello groovy!"  
})

可以看見,當閉包作為閉包或方法的最後一個引數時我們可以將閉包從引數圓括號中提取出來接在最後,如果閉包是唯一的一個引數,則閉包或方法引數所在的圓括號也可以省略;對於有多個閉包引數的,只要是在引數宣告最後的,均可以按上述方式省略。

6 GDK(Groovy Development Kit)

Groovy除了可以直接使用Java的JDK以外還有自己的一套GDK,其實也就是對JDK的一些類的二次封裝罷了;一樣,這是GDK官方API文件,寫程式碼中請自行查閱。

6-1 I/O操作

Groovy提供了很多IO操作的方法,你可以使用Java的那寫IO方法,但是沒有Groovy的GDK提供的簡單牛逼。

讀檔案操作:

我們先來看一個例子:


//讀檔案列印指令碼
new File('/home/temp', 'haiku.txt').eachLine { line ->
    println line
}

//讀檔案列印及列印行號指令碼
new File(baseDir, 'haiku.txt').eachLine { line, nb ->
    println "Line $nb: $line"
}

可以看見,這是一個讀檔案列印每行的指令碼,eachLine方法是GDK中File的方法,eachLine的引數是一個閉包,這裡採用了簡寫省略括弧。

當然了,有時候你可能更加喜歡用Reader來操作,使用Reader時即使丟擲異常也會自動關閉IO。如下:

def count = 0, MAXSIZE = 3
new File(baseDir,"haiku.txt").withReader { reader ->
    while (reader.readLine()) {
        if (++count > MAXSIZE) {
            throw new RuntimeException('Haiku should only have 3 verses')
        }
    }
}

接著我們再看幾個關於讀檔案的操作使用,如下:

//把讀到的檔案行內容全部存入List列表中
def list = new File(baseDir, 'haiku.txt').collect {it}
//把讀到的檔案行內容全部存入String陣列列表中
def array = new File(baseDir, 'haiku.txt') as String[]
//把讀到的檔案內容全部轉存為byte陣列
byte[] contents = file.bytes

//把讀到的檔案轉為InputStream,切記此方式需要手動關閉流
def is = new File(baseDir,'haiku.txt').newInputStream()
// do something ...
is.close()

//把讀到的檔案以InputStream閉包操作,此方式不需要手動關閉流
new File(baseDir,'haiku.txt').withInputStream { stream ->
    // do something ...
}

上面介紹了一些常用的檔案讀操作,其它的具體參見API和GDK吧。

寫檔案操作:

有了上面的讀操作,接下來直接看幾個寫操作的例子得了,如下:

//向一個檔案以utf-8編碼寫三行文字
new File(baseDir,'haiku.txt').withWriter('utf-8') { writer ->
    writer.writeLine 'Into the ancient pond'
    writer.writeLine 'A frog jumps'
    writer.writeLine 'Water’s sound!'
}
//上面的寫法可以直接替換為此寫法
new File(baseDir,'haiku.txt') << '''Into the ancient pond
A frog jumps
Water’s sound!'''
//直接以byte陣列形式寫入檔案
file.bytes = [66,22,11]
//類似上面讀操作,可以使用OutputStream進行輸出流操作,記得手動關閉
def os = new File(baseDir,'data.bin').newOutputStream()
// do something ...
os.close()
//類似上面讀操作,可以使用OutputStream閉包進行輸出流操作,不用手動關閉
new File(baseDir,'data.bin').withOutputStream { stream ->
    // do something ...
}

上面介紹了一些常用的檔案寫操作,其它的具體參見API和GDK吧。

檔案樹操作:

在指令碼環境中,遍歷一個檔案樹是很常見的需求,Groovy提供了多種方法來滿足這個需求。如下:

//遍歷所有指定路徑下檔名列印
dir.eachFile { file ->                      
    println file.name
}
//遍歷所有指定路徑下符合正則匹配的檔名列印
dir.eachFileMatch(~/.*\.txt/) { file ->     
    println file.name
}
//深度遍歷列印名字
dir.eachFileRecurse { file ->                      
    println file.name
}
//深度遍歷列印名字,只包含檔案型別
dir.eachFileRecurse(FileType.FILES) { file ->      
    println file.name
}
//允許設定特殊標記規則的遍歷操作
dir.traverse { file ->
    if (file.directory && file.name=='bin') {
        FileVisitResult.TERMINATE                   
    } else {
        println file.name
        FileVisitResult.CONTINUE                    
    }
}

執行外部程式:

Groovy提供一種簡單方式來處理執行外部命令列後的輸出流操作。如下:

def process = "ls -l".execute()             
println "Found text ${process.text}"

execute方法返回一個java.lang.Process物件,支援in、out、err的資訊反饋。在看一個例子,如下:

def process = "ls -l".execute()             
process.in.eachLine { line ->               
    println line                            
}

上面使用閉包操作打印出執行命令列的輸入流資訊。

6-2 有用的工具類操作

ConfigSlurper配置:

ConfigSlurper是一個配置管理檔案讀取工具類,類似於Java的*.properties檔案,如下:

def config = new ConfigSlurper().parse('''
    app.date = new Date()  
    app.age  = 42
    app {                  
        name = "Test${42}"
    }
''')

assert config.app.date instanceof Date
assert config.app.age == 42
assert config.app.name == 'Test42'

上面介紹了一些常用的屬性配置操作,其它的具體參見API和GDK吧。

Expando擴充套件:

def expando = new Expando()
expando.toString = { -> 'John' }
expando.say = { String s -> "John says: ${s}" }

assert expando as String == 'John'
assert expando.say('Hi') == 'John says: Hi'

上面介紹了一些常用的拓展操作,其它的具體參見API和GDK吧。

6-2 其他操作

還有很多其他操作,這裡就不一一列舉,詳情參考官方文件即可,譬如JSON處理、XML解析啥玩意的,自行需求摸索吧。

7 DSL(Domain Specific Languages)領域相關語言

這個就不特殊說明了,只在這裡提一下,因為我們前邊很多地方已經用過它了,加上我們只是乾貨基礎掌握,所以不做深入探討。

DSL是一種特定領域的語言(功能領域、業務領域),Groovy是通用的程式語言,所以不是DSL,但是Groovy卻對編寫全新的DSL提供了很好的支援,這些支援來自於Groovy自身