Groovy是一門基于JVM的動態(tài)語言磨澡,很多語法和Java類似懊直。大部分Java代碼也同時是合法的Groovy代碼指巡。本文是快速入門淑履,所以針對語法并不會做非常詳細的介紹。如果需要詳細語法藻雪,請直接查看Groovy官方文檔秘噪。另外為了省事,本文中的大部分代碼例子直接引用了Groovy文檔勉耀。
基本內容
注釋
單行注釋缆娃,以//
開頭。
多行注釋瑰排,/* */
。
GroovyDoc注釋暖侨,和JavaDoc注釋類似椭住。
/**
* Creates a greeting method for a certain person.
*
* @param otherPerson the person to greet
* @return a greeting message
*/
Shebang注釋,和Linux其他注釋類似字逗,用于指定腳本解釋器的位置京郑。
#!/usr/bin/env groovy
標識符
大體上,Groovy標識符的命名規(guī)則和Java差不多葫掉。如果某個標識符在Groovy中合法些举,在Java中不合法,我們可以使用單引號或雙引號將標識符包括起來俭厚。
字符串
字符串可以使用單引號'
或雙引號"
包括起來户魏。
def str="1234"
def str2='1234'
多行字符串可以使用三個連續(xù)的單引號或雙引號包括。不論是單行還是多行字符串, 都可以使用反斜杠轉義字符叼丑。
def multiline="""line1
line2
line3
"""
我們還可以將變量直接插入到字符串中关翎,這叫做內插字符串(String interpolation)。語法和EL表達式類似鸠信。編譯器會把美元和花括號中的內容替換成實際的值纵寝,內插字符串中還可以進行表達式計算。
def name = 'Guillaume' // a plain string
def greeting = "Hello ${name}"
當內插字符串可以由前后的符號區(qū)分出的時候星立,花括號可以省略爽茴。
def person = [name: 'Guillaume', age: 36]
assert "$person.name is $person.age years old" == 'Guillaume is 36 years old'
當使用內插字符串的時候,字符串字面值是Groovy的字符串類型GString绰垂。這一點需要注意室奏。普通的Java字符串是不變的,而GString是可變的辕坝。另外它們的哈希值也不同窍奋。因此在使用Map等數(shù)據(jù)類型的時候需要格外注意,避免使用GString作為Map的鍵酱畅。
字符
Groovy沒有字符字面量琳袄。如果需要向Java方法傳入單個字符的話,可以使用下面的方法進行轉換纺酸。
def c2 = 'B' as char
assert c2 instanceof Character
def c3 = (char)'C'
assert c3 instanceof Character
布爾類型
Groovy的布爾類型和Java類似窖逗,也有true
和false
兩個值。不過Groovy的布爾語義更豐富餐蔬。未到結尾的迭代器碎紊、非空對象引用、非零數(shù)字都認為是真樊诺;空集合仗考、空字符串等認為是假。詳情參見Groovy文檔 core-semantics#Groovy-Truth
數(shù)字類型
Groovy支持byte
词爬、char
秃嗜、short
、 int
顿膨、long
和 BigInteger
等幾種數(shù)字類型锅锨。如果使用普通方式聲明,它們和Java中的變量很相似恋沃。
int i = 4
BigInteger bi = 6
如果使用def
關鍵字聲明必搞,那么這些數(shù)字會自動選擇可以容納它們的類型。
def a = 1
assert a instanceof Integer
// Integer.MAX_VALUE
def b = 2147483647
assert b instanceof Integer
// Integer.MAX_VALUE + 1
def c = 2147483648
assert c instanceof Long
// Long.MAX_VALUE
def d = 9223372036854775807
assert d instanceof Long
// Long.MAX_VALUE + 1
def e = 9223372036854775808
assert e instanceof BigInteger
這些整數(shù)還可以添加0b
囊咏、0
和0x
前綴恕洲,分別代表8進制數(shù)塔橡,8進制數(shù)和16進制數(shù)。
另外Groovy還支持float
研侣、double
和BigDecimal
三種浮點數(shù)類型谱邪。原理同上。還可以使用科學計數(shù)法1.123E10
這樣的形式代表浮點數(shù)庶诡。
Groovy的數(shù)字常量同樣支持后綴區(qū)分字面值類型惦银,這幾種類型和Java中的類似。唯一不同的是Groovy還支持G
和g
后綴末誓,代表BigInteger或BigDecimal類型扯俱,根據(jù)字面值是否含有小數(shù)點來區(qū)分。
數(shù)學計算
數(shù)字的計算結果和Java規(guī)則類似:小于int的整數(shù)類型會被提升為int類型喇澡,計算結果也是int類型迅栅;小于long的整數(shù)類型和long計算,結果是long類型晴玖;BigInteger和其它整數(shù)類型計算读存,結果是BigInteger類型;BigDecimal和其它整數(shù)類型計算呕屎,結果是BigDecimal類型让簿;BigDecimal和float、double等類型計算秀睛,結果是double類型尔当。
列表
Groovy中的列表比較靈活,有點像Python中的列表蹂安。使用[....]
語法可以聲明列表椭迎,默認情況下列表是ArrayList實現(xiàn)。我們也可以使用as
運算符自己選擇合適的列表底層類型田盈。
def arrayList = [1, 2, 3]
assert arrayList instanceof java.util.ArrayList
def linkedList = [2, 3, 4] as LinkedList
assert linkedList instanceof java.util.LinkedList
有了列表之后畜号,就可以使用它了。使用方法和Python差不多允瞧。我們使用[索引]
引用和修改列表元素简软。如果索引是負的,則從后往前計數(shù)瓷式。要在列表末尾添加元素,可以使用左移運算符<<
语泽。如果在方括號中指定了多個索引贸典,會返回由這些索引對應元素組成的新列表。使用兩個點加首位索引..
可以選擇一個子列表踱卵。
def letters = ['a', 'b', 'c', 'd']
assert letters[0] == 'a'
assert letters[1] == 'b'
assert letters[-1] == 'd'
assert letters[-2] == 'c'
letters[2] = 'C'
assert letters[2] == 'C'
letters << 'e'
assert letters[ 4] == 'e'
assert letters[-1] == 'e'
assert letters[1, 3] == ['b', 'd']
assert letters[2..4] == ['C', 'd', 'e']
列表還可以組合成復合列表廊驼。
def multi = [[0, 1], [2, 3]]
assert multi[1][0] == 2
數(shù)組
聲明數(shù)組的方式和列表一樣据过,只不過需要顯示指定數(shù)組類型。數(shù)組的使用方法也和列表類似妒挎,只不過由于數(shù)組是不可變的绳锅,所以不能像數(shù)組末尾添加元素。
int[] intArray = [1, 2, 3, 4, 5]
def intArray2 = [1, 2, 3, 4, 5, 6] as int[]
Map
創(chuàng)建Map同樣使用方括號酝掩,不過這次需要同時指定鍵和值了鳞芙。Map創(chuàng)建好之后,我們可以使用[鍵]
或.鍵
來訪問對應的值期虾。默認情況下創(chuàng)建的Map是java.util.LinkedHashMap
原朝,我們可以聲明變量類型或者使用as
關鍵字改變Map的實際類型。
def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF']
assert colors['red'] == '#FF0000'
assert colors.green == '#00FF00'
關于Map有一點需要注意镶苞。如果將一個變量直接作為Map的鍵的話喳坠,其實Groovy會用該變量的名稱作為鍵,而不是實際的值茂蚓。如果需要講變量的值作為鍵的話壕鹉,需要在變量上添加小括號。
def key = 'name'
def person = [key: 'Guillaume'] //鍵是key而不是name
assert !person.containsKey('name')
assert person.containsKey('key')
//這次才正確的將key變量的值作為Map的鍵
person = [(key): 'Guillaume']
assert person.containsKey('name')
assert !person.containsKey('key')
運算符
和Java類似的運算符
Groovy的數(shù)學運算符和Java類似聋涨,只不過多了一個乘方運算**
和乘方賦值**=
晾浴。
Groovy的關系運算符(大于、小于等于這些)和Java類似牛郑。
Groovy的邏輯運算符(與或非這些)和Java類似怠肋,也支持短路計算。
Groovy的位運算符合Java類似淹朋。
Groovy的三元運算符條件?值1:值2
和Java類似笙各。
可空運算符
Groovy支持Elvis操作符,當對象非空的時候結果是值1础芍,為空時結果是值2杈抢。或者更直接仑性,對象非空是使用對象本身惶楼,為空時給另一個值,常用于給定某個可空變量的默認值诊杆。
displayName = user.name ? user.name : 'Anonymous'
displayName = user.name ?: 'Anonymous'
安全導航運算符
當調用一個對象上的方法或屬性時歼捐,如果該對象為空,就會拋出空指針異常晨汹。這時候可以使用?.
運算符豹储,當對象為空時表達式的值也是空,不會拋出空指針異常淘这。
def person = Person.find { it.id == 123 }
def name = person?.name
assert name == null
字段訪問運算符
在Groovy中默認情況下使用點運算符.
會引用屬性的Getter或Setter剥扣。如果希望直接訪問字段巩剖,需要使用.@
運算符。
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的閉包Closure晦炊。這種運算符可以將方法作為參數(shù)鞠鲜,讓Groovy語言非常靈活。
def str = 'example of method reference'
def fun = str.&toUpperCase
def upper = fun()
assert upper == str.toUpperCase()
展開運算符
展開運算符*.
會調用一個列表上所有元素的相應方法或屬性刽锤,然后將結果再組合成一個列表镊尺。
class Car {
String make
String model
}
def cars = [
new Car(make: 'Peugeot', model: '508'),
new Car(make: 'Renault', model: 'Clio')]
def makes = cars*.make
assert makes == ['Peugeot', 'Renault']
展開運算符是空值安全的,如果遇到了null值并思,不會拋出空指針異常庐氮,而是返回空值。
cars = [
new Car(make: 'Peugeot', model: '508'),
null,
new Car(make: 'Renault', model: 'Clio')]
assert cars*.make == ['Peugeot', null, 'Renault']
assert null*.make == null
展開運算符還可以用于展開方法參數(shù)宋彼、列表和Map弄砍。
范圍運算符
使用..
創(chuàng)建范圍。默認情況下范圍是閉區(qū)間输涕,如果需要開閉區(qū)間可以在結束范圍上添加<
符號音婶。范圍的類型是groovy.lang.Range
,它繼承了List
接口莱坎,也就是說我們可以將范圍當做List使用衣式。
def range = 0..5
assert (0..5).collect() == [0, 1, 2, 3, 4, 5]
assert (0..<5).collect() == [0, 1, 2, 3, 4]
assert (0..5) instanceof List
assert (0..5).size() == 6
比較運算符
<=>
運算符相當于調用compareTo方法。
assert (1 <=> 1) == 0
成員運算符
成員運算符in
相當于調用contains或isCase方法檐什。
def list = ['Grace','Rob','Emmy']
assert ('Emmy' in list)
相等運算符
==
運算符和Java中的不同碴卧。在Groovy中它相當于調用equals
方法。如果需要比較引用乃正,使用is
住册。
def list1 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3']
def list2 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3']
assert list1 == list2 //比較內容相等
assert !list1.is(list2) //比較引用相等
轉換運算符
我們可以使用Java形式的(String) i
來轉換類型。但是假如類型不匹配的話瓮具,就會拋出ClassCastException
荧飞。而使用as
運算符就會避免這種情況。
Integer x = 123
String s = x as String
如果希望自己的類也支持as
運算符的話名党,需要實現(xiàn)asType
方法叹阔。
表達式和語句
聲明變量
Groovy支持以傳統(tǒng)方式使用變量類型 變量名
的方式聲明變量,也可以使用def
關鍵字聲明變量传睹。使用def
關鍵字的時候耳幢,變量類型由編譯器自動推斷,無法推斷時就是Object
類型蒋歌。
Groovy可以同時聲明多個變量帅掘。
def (a, b, c) = [10, 20, 'foo']
如果左邊的變量數(shù)比右面的值多,那么剩余的變量就是null堂油。
def (a, b, c) = [1, 2]
assert a == 1 && b == 2 && c == null
如果等號右面比左面多修档,那么多余的值會被忽略。
def (a, b) = [1, 2, 3]
assert a == 1 && b == 2
自定義對象也可以用多重賦值進行對象解構府框。該對象必須有getAt方法吱窝。
class CustomDestruction {
int a
int b
int c
//解構需要實現(xiàn)getAt方法
def getAt(int i) {
switch (i) {
case 0: a; break
case 1: b; break
case 2: c; break
default: throw new IllegalArgumentException()
}
}
static void main(String[] args) {
//對象解構
def obj = new CustomDestruction(a: 3, b: 4, c: 5)
def (x, y, z) = obj
println("x=$x,y=$y,z=$z")
}
}
條件語句
Groovy的if語句和Java的類似。不過在Groovy中布爾值的真假不僅看條件比較的結果迫靖,還可以以其他情況判斷院峡。前面已經(jīng)介紹過了。switch語句同理系宜,真值判斷非常自由照激。詳情可參見Groovy文檔 真值判斷。
循環(huán)語句
Groovy支持傳統(tǒng)的Java的for(int i=0;i<N;i++)
和for(int i :array)
兩種形式盹牧。另外還支持for in loop形式俩垃,支持迭代范圍、列表汰寓、Map口柳、數(shù)組等多種形式的集合。
// 迭代范圍
def x = 0
for ( i in 0..9 ) {
x += i
}
assert x == 45
// 迭代列表
x = 0
for ( i in [0, 1, 2, 3, 4] ) {
x += i
}
while語句的形式和Java相同有滑。
斷言語句
前面我們看到了很多Groovy斷言的例子跃闹。Groovy斷言語句的功能很強大,以至于文檔中寫的是強力斷言(Power assertion)毛好。
Groovy斷言的形式如下望艺。Groovy斷言和Java斷言完全不同。Groovy斷言是一項語言功能睛榄,一直處于開啟狀態(tài)荣茫,和JVM的斷言功能-ea
完全無關。所以它是我們進行單元測試的首選方式场靴。
assert [left expression] == [right expression] : (optional message)
比如我們要斷言1+1=3啡莉。結果應該類似這樣。越復雜的表達式旨剥,斷言效果越清晰咧欣。有興趣的同學可以試試。
Caught: Assertion failed:
assert 1+1 == 3
| |
2 false
面向對象編程
Groovy的面向對象編程和Java類似轨帜,但是提供了一系列功能簡化面向對象開發(fā)魄咕。當然如果你想使用傳統(tǒng)的Java語法來聲明所有成員也可以,Groovy設計目的之一就是讓Java程序員能夠以低成本的方式切換到Groovy上蚌父。
- 字段默認是私有的哮兰,Groovy會自動實現(xiàn)Getter和Setter方法毛萌。
- 方法和屬性默認是公有的。
- 類不必和文件名相同喝滞,
- 一個文件可以有多個類阁将,如果一個類也沒有,該文件就會被看做是腳本右遭。
構造器
Groovy的構造器非常靈活做盅,我們可以使用傳統(tǒng)的Java方式聲明和使用構造器,也可以完全不聲明構造器窘哈。有時候不聲明反而更簡單吹榴。如果沒有聲明構造器的話,我們可以在構造對象的時候使用命名參數(shù)方式傳遞參數(shù)滚婉,這種方式非常方便图筹,因為我們不需要聲明所有參數(shù),只要聲明所需的參數(shù)即可让腹。
如果希望對構造器進行限制婿斥,可以手動聲明構造器,這樣這種自動構造就不會進行了哨鸭。
class Product {
String name
double price
String toString() {
return "Product(name:$name,price:$price)"
}
static void main(String[] args) {
def product = new Product(name: 'AMD Ryzen 1700', price: 2499)
println("隱式構造器:$product")
}
}
方法
Groovy方法和Java方法基本相同民宿。不過Groovy方法更方便:支持命名參數(shù)和默認參數(shù)。另外Groovy方法可以使用def
關鍵字聲明像鸡,這時候方法返回類型是Object活鹰。在Groovy中方法的返回語句可以省略,這時候編譯器會使用方法的最后一個語句的值作為返回值只估。在前面我們還看到了def
關鍵字定義變量志群,這時候變量的類型需要從代碼中推斷。
在使用命名參數(shù)的時候需要注意一點蛔钙,方法參數(shù)需要聲明為Map類型(不需要詳細指定鍵和值的類型)锌云,在調用方法的時候使用命名參數(shù)方式傳入?yún)?shù)。
def foo(Map args) { "${args.name}: ${args.age}" }
//調用方法
foo(name: 'Marie', age: 1)
另外方法的括號是可選的吁脱,我們可以省略括號直接像這樣調用方法桑涎。
methodWithDefaultParam '555', 42
字段
Groovy中字段和Java中的概念類似。不過Groovy更加方便:默認情況下字段是私有的兼贡,Groovy自動生成字段的Getter和Setter攻冷。如果需要更精細的控制,把它當成Java字段用就行了遍希。不過如果自定義字段的話等曼,Groovy不會自動生成對應的屬性了。
屬性
如果字段上面沒有聲明訪問修飾符(private、public這些)禁谦,Groovy就會自動生成Gettter和Setter胁黑。如果字段是final
的,那么只會生成Getter州泊。這就是Groovy方便的屬性功能别厘。
當然Groovy的方便不止于此,我們的所有類似Java訪問字段的語法拥诡,實際上都會調用字段對應的Getter和Setter。這樣顯著減少了代碼量氮发。如果在類內部的話渴肉,.字段
語法會直接訪問字段,這樣做是為了防止無限遞歸調用屬性爽冕。
下面的例子中仇祭,第一次調用p.name = 'Marge'
如果在類內部,就直接寫入字段颈畸,如果調用在類外部乌奇,就會使用Setter寫入。第二次調用使用了方法語法眯娱,直接使用Setter寫入礁苗,所以不管在類內還是類外,寫入的值都是"Wonder$name"
徙缴。
class Person {
String name
void name(String name) {
this.name = "Wonder$name"
}
String wonder() {
this.name
}
}
def p = new Person()
p.name = 'Marge'
assert p.name == 'Marge'
p.name('Marge')
assert p.wonder() == 'WonderMarge'
特征類
Groovy和Scala一樣试伙,支持特征類(trait)。特征類就好像自帶實現(xiàn)的接口于样。在Java中只能繼承一個類和多個接口疏叨。在Groovy中,我們可以繼承多個特征類穿剖。特征類和普通的Groovy類一樣蚤蔓,可以包括屬性、字段糊余、方法等秀又,特征類也可以是抽象的。
使用特征類贬芥,我們可以在Groovy中實現(xiàn)類似C++的多重繼承涮坐。另外,特征類支持運行時動態(tài)綁定誓军,在某些情況下非常有用袱讹。
trait Readable {
void read() {
println("read...")
}
}
trait Writable {
void write(String text) {
println("write $text")
}
}
class Notebook implements Readable, Writable {
static void main(String[] args) {
//使用特性類
def notebook = new Notebook()
notebook.read()
notebook.write("something")
}
}
閉包
閉包是Groovy非常重要的一個功能,也是我們介紹的最后一個功能。要了解閉包捷雕,最好先知道Java的Lambda表達式椒丧、匿名內部類等概念。Groovy閉包和Lambda表達式概念相近救巷,但是功能更強大壶熏。
聲明閉包
閉包的形式如下。
{ [closureParameters -> ] statements }
以下都是合法的Groovy閉包浦译。所有閉包都是groovy.lang.Closure
類型的實例棒假。閉包的參數(shù)類型是可選的。如果閉包只有單個參數(shù)精盅,參數(shù)名也是可選的帽哑。Groovy會隱式指定it
作為參數(shù)名。Kotlin語言也是類似的做法叹俏,有助于我們先出可讀性很好的閉包妻枕。
{ item++ }
{ -> item++ }
{ println 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()
}
調用閉包
閉包既可以當做方法來調用,也可以顯示調用call方法粘驰。
def code = { 123 }
assert code() == 123
assert code.call() == 123
調用有參數(shù)的閉包也是類似的屡谐。
def isOdd = { int i-> i%2 == 1 }
assert isOdd(3) == true
assert isOdd.call(2) == false
使用閉包
Groovy閉包類似Java的Lambda表達式和匿名內部類,不過使用更方便蝌数,能讓我們減少不少代碼量愕掏。閉包還可以作為方法參數(shù)傳遞到其他地方,這讓閉包更加靈活顶伞。
static void funWithClosure(Closure closure) {
closure()
}
//在其他地方調用該方法
funWithClosure({ println("Hello yitian") })
//括號還可以省略亭珍,更加簡練
funWithClosure { println("Hello yitian") }
關于閉包,還有幾個精彩的例子枝哄,就是Gradle腳本和Groovy模板引擎肄梨,它們都利用了Groovy強大的閉包功能,構建出簡練而強大的DSL挠锥,讓我們用很少的代碼就可以實現(xiàn)強大的功能(雖然學起來稍微復雜點)众羡。