目錄
本文主要介紹 Groovy 語言的操作符,基於 Groovy 2.5.5 版本。
1. 算術操作符
Groovy 支持你所熟知的來自數學或其他編程語言中的常用算術運算符。Groovy 支持 Java 中的所有算術運算符。我們將在下面的一系列示例中學習它們。
1.1 普通算術操作符
Groovy 支持的二元算術操作符如下表所示:
操作符 | 用途 | 說明 |
---|---|---|
|
相加 |
|
|
相減 |
|
|
相乘 |
|
|
相除 |
如果想使用整數除法,請使用 intdiv() 方法,請同時查看 integer division 章節瞭解更多關於整數除法的返回值類型信息。 |
|
取餘 |
|
|
乘方 |
請查看 the power operation 章節來了解更多關於乘方操作符的返回值信息。 |
下面是關於這些操作符用法的一些例子:
assert 1 + 2 == 3
assert 4 - 3 == 1
assert 3 * 5 == 15
assert 3 / 2 == 1.5
assert 10 % 3 == 1
assert 2 ** 3 == 8
1.2 一元操作符
+ 和 - 操作符也可以被當作一元操作符來使用:
assert +3 == 3
assert -4 == 0 - 4
assert -(-1) == 1 //1
//1:將一元操作符 - 應用到表達式上時,需要在表達式上添加括號
在一元算術操作符方面,Groovy 也同時支持前綴和後綴形式的自增(++)和自減(--)操作符:
def a = 2
def b = a++ * 3 //1
assert a == 3 && b == 6
def c = 3
def d = c-- * 2 //2
assert c == 2 && d == 6
def e = 1
def f = ++e + 3 //3
assert e == 2 && f == 5
def g = 4
def h = --g + 1 //4
assert g == 3 && h == 4
//1: 後綴自增操作符會在表達式求值完成後,對 a 執行自增操作,並將表達式的值賦值給 b
//2: 後綴自減操作符會在表達式求值完成後,對 c 執行自減操作,並將表達式的值賦值給 d
//3: 前綴自增操作符會先對 e 執行自增操作,然後對表達式進行求值並賦值給 f
//4: 前綴自減操作符會先對 g 執行自減操作,然後對表達式進行求值並賦值給 h
1.3 賦值算術操作符
前面我們看到的各種二元算術操作符都具有對應的賦值算術操作符形式:
-
+=
-
-=
-
*=
-
/=
-
%=
-
**=
我們來看些實例:
def a = 4
a += 3
assert a == 7
def b = 5
b -= 3
assert b == 2
def c = 5
c *= 3
assert c == 15
def d = 10
d /= 2
assert d == 5
def e = 10
e %= 3
assert e == 1
def f = 3
f **= 2
assert f == 9
2. 關係運算符
關係操作符允許在對象之間執行比較運算,以確定兩個對象是否相同,或者是否一個比另一個大、小或等於另一個。Groovy 中有如下的關係運算符:
操作符 | 用途 |
---|---|
|
等於 |
|
不等於 |
|
小於 |
|
小於等於 |
|
大於 |
|
大於等於 |
下面是使用這些操作符進行簡單的算術比較的例子:
assert 1 + 2 == 3
assert 3 != 4
assert -2 < 3
assert 2 <= 2
assert 3 <= 4
assert 5 > 1
assert 5 >= -2
3. 邏輯運算符
Groovy 爲布爾表達式提供了 3 個邏輯運算符:
-
&&
: 邏輯與 -
||
: 邏輯或 -
!
: 邏輯非
用下面例子作簡單演示:
assert !false
assert true && true
assert true || false
3.1 優先級
邏輯非 !運算符的優先級高於邏輯與 && 運算符:
assert (!false && false) == false //1
//1: 這裏斷言爲真(因爲括號內的表達式爲假),因爲邏輯非運算符的優先級高於邏輯與運算符,所有邏輯非操作符只作用於第一個 false,不然的話,它就得作用於邏輯與運算的結果,導致斷言失敗。
邏輯與 && 運算符的優先級高於邏輯或 || 運算符:
assert true || true && false //1
//1: 這裏斷言爲真,因爲 && 運算符的優先級比 || 運算符高,因此 || 運算最後執行,並返回真值。
3.2 短路特性
邏輯或 || 運算符具有短路特性:如果運算符的左操作數爲真,那麼它就已經知道結果無論如何都是真,因此右操作數就不會執行計算。僅當左操作數爲假時,右操作數纔會執行計算。
類似地,邏輯與操作也具有短路特性:如果運算符的左操作數爲假,那麼它就已經知道結果無論如何都是假,因此右操作數就不會執行計算。僅當左操作數爲真時,右操作數纔會執行計算。
boolean checkIfCalled() { //1
called = true
}
called = false
true || checkIfCalled()
assert !called //2
called = false
false || checkIfCalled()
assert called //3
called = false
false && checkIfCalled()
assert !called //4
called = false
true && checkIfCalled()
assert called //5
//1: 創建一個叫做 checkIfCalled 的函數,它內部會將 called 變量設置爲 true
//2: 將 called 標記置爲 false 後,我們驗證下 || 操作符的左操作數爲真時,不會對右操作數執行計算,因爲 || 操作符具有短路特性
//3: 由於 || 操作符的左操作數爲假,因此會對又操作數執行計算,導致 called 標記變爲 true
//4: 類似的,我們驗證 && 操作符的左操作數爲假時,不會對右操作數執行計算
//5: 但是當 && 操作符的左操作數爲真時,會對右操作數執行計算
4. 位運算操作符
Groovy 中有 4 個位運算操作符:
-
&
: 位與 -
|
: 位或 -
^
: 位異或 -
~
: 按位取反
位運算操作符可以應用於 byte 或 int 型數據,並返回一個 int 值:
int a = 0b00101010
assert a == 42
int b = 0b00001000
assert b == 8
assert (a & a) == a // 位與
assert (a & b) == b // 位與並返回同時爲 1 的比特
assert (a | a) == a // 位或
assert (a | b) == a // 位或並返回所有爲 1 的比特
int mask = 0b11111111 // 設置一個只檢查最後 8 位的掩碼
assert ((a ^ a) & mask) == 0b00000000 // 對自身進行按位異或將返回 0
assert ((a ^ b) & mask) == 0b00100010 // 位異或
assert ((~a) & mask) == 0b11010101 // 按位取反
值得注意的是,原始類型的內部表示遵循 Java 語言規範。特別地,原始類型是有符號的,這意味着對於按位取反操作,通常比較好的實踐是:使用掩碼來提取那些必需的比特。
在 Groovy 中,位操作符是可以重載的,意味着你可以針對任意類型的對象定義這些操作符作用在其上的行爲。
5. 條件運算符
5.1 非運算符
非運算符由感嘆號 !表示,它會反轉底層布爾表達式的計算結果。特別是非運算符可以和 Groovy 真值(Groovy truth)結合使用:
assert (!true) == false // 非 true 即 false
assert (!'foo') == false // 'foo' 是非空字符串,求值後爲 true, 取非後得到 false
assert (!'') == true // '' 是空字符串,求值後爲 false, 取非後得到 true
5.2 三元運算符
三元操作符是一個縮寫的表達式,它等效於一個由 if/else 分支語句組織起來的賦值語句。
除了使用下面的這個 if/else 語句外:
if (string!=null && string.length()>0) {
result = 'Found'
} else {
result = 'Not found'
}
你可以把它簡寫成:
result = (string!=null && string.length()>0) ? 'Found' : 'Not found'
三元操作符也是可以和 Groovy 真值(Groovy truth)結合使用的,所有上面的寫法可以進一步簡化:
result = string ? 'Found' : 'Not found'
5.3 埃爾維斯操作符
埃爾維斯操作符是三元運算符的縮寫形式。使用這種便捷寫法的一個實際場景是:如果一個表達式求值爲假(基於 Groovy 真值)時需要返回一個合理的默認值的情況。下面是個簡單的例子:
displayName = user.name ? user.name : 'Anonymous' //1
displayName = user.name ?: 'Anonymous' //2
//1: 使用三元操作符時,你必須重複你想要賦值的那個值
//2: 使用埃爾維斯操作符時,如果被測試值爲真時,就會使用該值作爲返回值
使用埃爾維斯操作符能夠降低代碼的複雜性也能夠減小代碼重構時發生錯誤的機率:因爲不需要在條件和真值返回值部分重複被測試的表達式。
6. 對象操作符
6.1 安全導航操作符
安全導航操作符 ?. 主要用來避免空指針異常(NullPointerException)。通常,當你有一個指向某個對象的引用時,在使用它進行方法或屬性訪問前,都需要檢查它是否爲 null。爲了避免這種檢查,安全導航操作符在引用爲 null 時,會直接返回 null,而不是拋出空指針異常。如下例所示:
def person = Person.find { it.id == 123 } // find 將會返回 null
def name = person?.name // 使用安全導航操作符可以避免空指針異常
assert name == null // 結果爲 null
6.2 直接字段訪問操作符
在 Groovy 中,當你寫了一段類似下面的代碼時:
class User {
public final String name // 公有字段 name
User(String name) { this.name = name}
String getName() { "Name: $name" } // name 的讀取器(getter),會返回一個定製化的字符串
}
def user = new User('Bob')
assert user.name == 'Name: Bob' // 此處會調用讀取器
user.name 調用會觸發一個到同名的屬性的調用,也就是說,在這裏會觸發對 name 的讀取器(getName)的調用。如果你的確是想獲取字段 name 的值,而不是調用它的讀取器,你就可以向下面這樣使用直接屬性訪問操作符 .@ 來實現:
assert user.@name == 'Bob' //1
//1: 使用 .@ 操作符強制訪問字段自身,而不是對應的獲取器
6.3 方法指針操作符
方法指針操作符(.&)被用來獲取方法的引用,並存儲到一個變量中,以便後續使用:
def str = 'example of method reference' // 變量 str 中存儲了一個字符串
def fun = str.&toUpperCase // 把 str 實例上的 toUpperCase 方法的引用存儲到變量 fun 中
def upper = fun() // 可以向普通的方法調用一樣調用 fun
assert upper == str.toUpperCase() // 結果和在 str 上直接調用 toUpperCase 方法是一樣的
使用方法指針具有許多優點。首先,方法指針的類型是 groovy.lang.Closure,所以在任何可以使用閉包的地方都可以使用方法指針。特別地,它適合用來轉換一個已有的方法以滿足策略模式的需求:
def transform(List elements, Closure action) { //1
def result = []
elements.each {
result << action(it)
}
result
}
String describe(Person p) { //2
"$p.name is $p.age"
}
def action = this.&describe //3
def list = [
new Person(name: 'Bob', age: 42),
new Person(name: 'Julia', age: 35)] //4
assert transform(list, action) == ['Bob is 42', 'Julia is 35'] //5
//1: transform 方法會對參數列表中的每一個元素調用 action 閉包,並返回一個新列表
//2: 定義一個接受 Person 類型參數,返回字符串的函數
//3: 創建一個指向 describe 函數的方法指針
//4: 創建一個參數列表
//5: 可以在需要閉包的地方使用方法指針
方法指針是綁定在方法接受者和方法名上的。方法參數是在運行時解析的,這就是說,如果你有多個相同名稱的方法,語法也是一樣的,並沒有什麼不同,只是解析具體要調用的方法是在運行時完成的:
def doSomething(String str) { str.toUpperCase() } //1
def doSomething(Integer x) { 2*x } //2
def reference = this.&doSomething //3
assert reference('foo') == 'FOO' //4
assert reference(123) == 246 //5
//1: 定義一個重載的 doSomething 方法,接受字符串類型參數
//2: 定義一個重載的 doSomething 方法,接受整形參數
//3: 創建一個指向 doSomething 的方法指針,並沒有指定參數類型
//4: 使用字符串參數調用方法指針時,會調用字符串版本的 doSomething 方法
//5: 使用整形參數調用方法指針時,會調用整形版本的 doSomething 方法
7. 正則表達式操作符
7.1 模式操作符
模式操作符(~)提供了一個創建 java.util.regex.Pattern 實例的簡單方式:
def p = ~/foo/
assert p instanceof Pattern
儘管你通常看到模式操作符一般和斜線風格的字符串一起使用,但其實它可以和任何形式的 Groovy 字符串一起使用:
p = ~'foo' //1
p = ~"foo" //2
p = ~$/dollar/slashy $ string/$ //3
p = ~"${pattern}" //4
//1: 使用單引號風格的字符串構建模式
//2: 使用雙引號風格的字符串構建模式
//3: 使用美元斜線風格的字符中構建模式,不用在字符串內對 $ 和 / 字符進行轉意
//4: 也可以使用 GString 創建模式
7.2 查找操作符
除了先構建一個模式外,也可以直接使用查找操作符(=~)來構建一個 java.util.regex.Matcher 對象:
def text = "some text to match"
def m = text =~ /match/ //1
assert m instanceof Matcher //2
if (!m) { //3
throw new RuntimeException("Oops, text not found!")
}
//1: 查找操作符 =~ 使用右側的模式對左側的 text 變量創建了一個 Matcher
//2: 查找操作符的返回類型是 Matcher
//3: 等效於調用 if(!m.find())
因爲 Matcher 對象強制轉換成 boolean 值是通過調用它的 find 方法實現的,所以查找操作符 =~ 用作判斷式時(在 if, while 語句中等)表現出的行爲和 Perl 語言中的 =~ 操作符一致。
7.3 匹配操作符
匹配操作符(==~)看起來像查找操作符(=~)的一個變種,它不返回 Matcher 對象,而是返回一個 boolean 值,並且要求輸入值與模式嚴格匹配:
m = text ==~ /match/ //1
assert m instanceof Boolean //2
if (m) { //3
throw new RuntimeException("Should not reach that point!")
}
//1: 匹配操作符會使用右側的模式來嚴格匹配左側的變量
//2: 匹配操作符的返回類型是布爾值
//3: 等價與調用 if(text ==~ /match/)
8. 其他操作符
8.1 展開操作符
展開點操作符(*.),簡稱爲展開操作符,通常被用來在聚合對象的每一個元素上執行操作。它等效於在聚合對象的每個元素上調用操作,然後把所有結果收集到一個列表裏:
class Car {
String make
String model
}
def cars = [
new Car(make: 'Peugeot', model: '508'),
new Car(make: 'Renault', model: 'Clio')] //1
def makes = cars*.make //2
assert makes == ['Peugeot', 'Renault'] //3
//1: 創建一個有 Car 類型元素組成的列表。列表是一個聚合對象。
//2: 在列表上調用展開操作符,訪問每個元素的 make 屬性
//3: 返回一個包含各個元素對應的 make 屬性所組成的列表
表達式 car*.make 等價於 car.collect { it.make }。當所訪問的屬性不是被操作列表自身的屬性時,Groovy 的 GPath 語法允許使用展開點操作符的縮寫形式,但是操作仍然會自動展開到列表的每一個元素上。在前面的例子中,我們就可以使用 car.make 這個簡寫形式,但是通常還是推薦顯式地寫出展開點操作符。
展開點操作符是 null 安全的,這意味着,如果被操作集合中有元素爲 null 時,將會返回 null,而不是拋出 NullPointerException 異常。
cars = [
new Car(make: 'Peugeot', model: '508'),
null, //1
new Car(make: 'Renault', model: 'Clio')]
assert cars*.make == ['Peugeot', null, 'Renault'] //2
assert null*.make == null //3
//1: 創建一個包含 null 元素的列表
//2: 使用展開操作符不會拋出空指針異常
//3: 展開操作符的接受者也可能爲 null,此時返回值也是 null
展開操作符可以被用於任何實現了 Iterable 接口的類上:
class Component {
Long id
String name
}
class CompositeObject implements Iterable<Component> {
def components = [
new Component(id: 1, name: 'Foo'),
new Component(id: 2, name: 'Bar')]
@Override
Iterator<Component> iterator() {
components.iterator()
}
}
def composite = new CompositeObject()
assert composite*.id == [1,2]
assert composite*.name == ['Foo','Bar']
當操作嵌套的聚合數據結構時,可以使用多個級聯的展開點操作符,如下例中的 cars*.models.*name :
class Make {
String name
List<Model> models
}
@Canonical
class Model {
String name
}
def cars = [
new Make(name: 'Peugeot',
models: [new Model('408'), new Model('508')]),
new Make(name: 'Renault',
models: [new Model('Clio'), new Model('Captur')])
]
def makes = cars*.name
assert makes == ['Peugeot', 'Renault']
def models = cars*.models*.name
assert models == [['408', '508'], ['Clio', 'Captur']]
assert models.sum() == ['408', '508', 'Clio', 'Captur'] // 展平一層
assert models.flatten() == ['408', '508', 'Clio', 'Captur'] // 展平所有層(此處僅有一層)
對於嵌套集合類的情況,可以考慮使用 collectNested DGM 方法,而不是展開點操作符:
class Car {
String make
String model
}
def cars = [
[
new Car(make: 'Peugeot', model: '408'),
new Car(make: 'Peugeot', model: '508')
], [
new Car(make: 'Renault', model: 'Clio'),
new Car(make: 'Renault', model: 'Captur')
]
]
def models = cars.collectNested{ it.model }
assert models == [['408', '508'], ['Clio', 'Captur']]
8.1.1 展開方法參數
有時可能你要調用的那個方法的參數已經存在於某個列表中,你必須做些適配把它們轉化爲方法的參數。在這樣情況下,你可以使用展開操作符來調用該方法。舉個例子,假如你有如下的方法簽名:
int function(int x, int y, int z) {
x*y+z
}
假設你還有下面這個列表:
def args = [4,5,6]
那麼你可以向下面這樣調用該方法,而不用定義任何中間變量:
assert function(*args) == 26
我們甚至允許混合正常參數和展開參數:
args = [4]
assert function(*args,5,6) == 26
8.1.2 展開列表元素
當在列表字面量中使用展開操作符時,效果看起來就像被展開的列表元素被直接內聯到了被操作的列表字面量中:
def items = [4,5] //1
def list = [1,2,3,*items,6] //2
assert list == [1,2,3,4,5,6] //3
//1: 定義一個列表 items
//2: 我們想把 items 列表中的元素直接插入到 list 列表中,而不調用 addAll 方法
//3: items 列表的內容被內聯到了 list 列表中
8.1.3 展開映射元素
展開映射操作符和展開列表操作符類似,只是它操作的是映射。它允許你將一個映射的元素內聯到另一個映射字面量中,如下例所示:
def m1 = [c:3, d:4] //1
def map = [a:1, b:2, *:m1] //2
assert map == [a:1, b:2, c:3, d:4] //3
//1: m1 是我們想要內聯的映射
//2: 我們使用 *:m1 的語法來將 m1 的內容展開到 map 映射中
//3: 現在 map 包含 m1 中的所有元素
展開映射操作符的使用位置是會對最終結果產生影響的,如下面例子所示:
def m1 = [c:3, d:4] //1
def map = [a:1, b:2, *:m1, d: 8] //2
assert map == [a:1, b:2, c:3, d:8] //3
//1: m1 是我們想要內聯的映射
//2: 我們使用 *:m1 的語法來將 m1 的內容展開到 map 映射中,但是在展開操作後,我們重新定義了鍵 d 的值
//3: 現在 map 包含 m1 中的所有的鍵,但是鍵 d 對應的值是修改後的
8.2 區間操作符
Groovy 支持區間的概念,並且提供了區間操作符(..)來創建區間對象:
def range = 0..5 //1
assert (0..5).collect() == [0, 1, 2, 3, 4, 5] //2
assert (0..<5).collect() == [0, 1, 2, 3, 4] //3
assert (0..5) instanceof List //4
assert (0..5).size() == 6 //5
//1: 一個由整數組成的區間
//2: 一個由整數組成的閉區間(包含首尾元素)
//3: 一個有整數組成的左閉右開區間(包含首元素,不包含尾元素)
//4: groovy.lang.Range 實現了 List 接口
//5: 可以在區間上調用 size() 方法
區間的實現是很輕量的,因爲只有起始和結尾元素會被存儲下來。你可以從任意具有 next() 和 previous() 方法且實現了 Comparable 接口的對象來創建區間。next 和 previous 方法分別用來確定區間裏的後一個和前一個元素。例如你可以向下面這樣創建一個字符組成的區間:
assert ('a'..'d').collect() == ['a','b','c','d']
8.3 比較操作符
比較操作符(Spaceship operator)內部其實是委派給 compareTo 方法的:
assert (1 <=> 1) == 0
assert (1 <=> 2) == -1
assert (2 <=> 1) == 1
assert ('a' <=> 'z') == -1
8.4 下標操作符
下標操作符 [] 是 getAt 或 putAt 方法的速寫,具體代表的含有主要要看操作符是位於賦值運算符的左側還是右側:
def list = [0,1,2,3,4]
assert list[2] == 2 //1
list[2] = 4 //2
assert list[0..2] == [0,1,4] //3
list[0..2] = [6,6,6] //4
assert list == [6,6,6,3,4] //5
//1: [2] 可以使用 .getAt(2) 替換
//2: 如果位於賦值操作符的左側,實際相當於調用 putAt
//3: getAt 也支持 Range 類型的參數
//4: 同樣 putAt 也支持 Range 類型的參數
//5: 列表被改變了
使用下標操作符,結合一個定製化的 getAt/putAt 實現,是一種方便的解析對象的方式:
class User {
Long id
String name
def getAt(int i) { //1
switch (i) {
case 0: return id
case 1: return name
}
throw new IllegalArgumentException("No such element $i")
}
void putAt(int i, def value) { //2
switch (i) {
case 0: id = value; return
case 1: name = value; return
}
throw new IllegalArgumentException("No such element $i")
}
}
def user = new User(id: 1, name: 'Alex') //3
assert user[0] == 1 //4
assert user[1] == 'Alex' //5
user[1] = 'Bob' //6
assert user.name == 'Bob' //7
//1: User 類型定義了一個定製化的 getAt 實現
//2: User 類型定義了一個定製化的 putAt 實現
//3: 創建一個 User 對象
//4: 使用下標操作符和索引 0 來獲取用戶的 id
//5: 使用下標操作符和索引 1 來獲取用戶的 name
//6: 可以使用下標操作符來給屬性賦值,這都歸功於底層對 putAt 方法的調用
//7: 校驗 name 屬性的確發生了變化
8.5 成員關係操作符
成員關係操作符(in)就等價於調用 isCase 方法。具體到一個 List 對象,它就等效於調用 contains 方法,請看下例:
def list = ['Grace','Rob','Emmy']
assert ('Emmy' in list) //1
//1: 等價於調用 list.contains('Emmy') 或 list.isCase('Emmy')
8.6 身份操作符
在 Groovy 中使用 == 進行相等性測試和 Java 中是有區別的。在 Groovy 中,它實際會調用 equals 方法。如果你想比較引用的相等性,你應該像下面的例子一樣使用 is 操作符:
def list1 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3'] //1
def list2 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3'] //2
assert list1 == list2 //3
assert !list1.is(list2) //4
//1: 創建一個字符串列表
//2: 創建另一個包含相同元素的字符串列表
//3: 使用 ==,我們是在測試對象的相等性
//4: 使用 is,我們是在檢查引用的相等性
8.7 強制轉換操作符
強制轉換操作符(as)是強制類型轉換的變種。它將對象強制地由一種類型轉換爲另一種類型,而不需要這兩種類型具有可賦值性。我們看個例子:
Integer x = 123
String s = (String) x //1
//1: Integer 和 String 類型之間不具有可賦值性,因此運行時該處將拋出 ClassCastException
可以通過強制轉換來修復該問題:
Integer x = 123
String s = x as String //1
//1: Integer 和 String 類型之間不具有可賦值性,但是使用 as 將會把這個整形強制轉換爲一個字符串
當一個對象被強制轉換成另一個類型的對象時,除非目標類型和源類型相同,否則強制轉換都將會返回一個新的對象。強制轉換的規則因着不同的源和目標類型而不同,並且如果兩個類型之間沒有轉換規則的話,強制轉換也可能會失敗。定製化的轉換規則可以使用 asType 方法來實現:
class Identifiable {
String name
}
class User {
Long id
String name
def asType(Class target) { //1
if (target == Identifiable) {
return new Identifiable(name: name)
}
throw new ClassCastException("User cannot be coerced into $target")
}
}
def u = new User(name: 'Xavier') //2
def p = u as Identifiable //3
assert p instanceof Identifiable //4
assert !(p instanceof User) //5
//1: User 類中定義了一個從 User 到 Identifiable 類的轉換規則
//2: 創建一個 User 類的實例
//3: 將 User 實例強制轉換成 Identifiable 類型的對象
//4: 強制轉換後的對象是 Identifiable 類的實例
//5: 強制轉換後的對象不再是 User 類的實例
8.8 鑽石操作符
鑽石操作符(<>)只是一個語法糖操作符,它的引入只是爲了兼容 Java 7 中的同名操作符。它用來表明泛型類型應該從聲明中推導:
List<String> strings = new LinkedList<>()
在動態類型的 Groovy 中,這個操作符完全用不到。在靜態類型檢查的 Groovy 中,該操作符也是可選的,因爲無論該操作符是否存在,Groovy 類型檢查器都會執行類型推斷。
8.9 方法調用操作符
方法調用操作符 () 被用來隱式地調用一個名叫 call 的方法。對於任意一個定義了 call 方法的對象,你都可以省略 .call 部分,而以方法調用操作符 () 代之:
class MyCallable {
int call(int x) { //1
2*x
}
}
def mc = new MyCallable()
assert mc.call(2) == 4 //2
assert mc(2) == 4 //3
//1: MyCallable 類定義了一個叫做 call 的方法。請注意,它不需要實現 java.util.concurrent.Callable 接口
//2: 使用常規的方法調用語法來調用 call 方法
//3: 可以省略 .call 部分,這都歸功於方法調用操作符
9. 操作符優先級
下面的表格按照優先級順序列出了所以 Groovy 操作符:
優先級 | 操作符 | 名稱 |
---|---|---|
1 |
|
對象創建,顯式括號 |
|
方法調用,閉包,列表/映射字面量 |
|
|
成員訪問,方法指針,字段/屬性直接訪問 |
|
|
安全導航,展開,展開點,展開映射 |
|
|
按位取反/模式,邏輯非,類型轉換 |
|
|
類標/映射/數組索引,後綴自增/自減 |
|
2 |
|
乘方 |
3 |
|
前綴自增/自減,正號,負號 |
4 |
|
乘,除,取餘 |
5 |
|
加,減 |
6 |
|
左移/右移,無符號右移,閉區間,左閉右開區間 |
7 |
|
小於,小於等於,大於,大於等於,成員操作符,實例判斷,強制類型轉換 |
8 |
|
等於,不等於,比較 |
|
正則查找,正則匹配 |
|
9 |
|
位與 |
10 |
|
位異或 |
11 |
|
位或 |
12 |
|
邏輯與 |
13 |
|
邏輯或 |
14 |
|
三目運算符 |
|
埃爾維斯運算符 |
|
15 |
|
各種賦值運算符 |
10. 操作符重載
Groovy 允許你重載各種操作符,以便你能夠在自定義類中使用它們。請看下面這個簡單的類:
class Bucket {
int size
Bucket(int size) { this.size = size }
Bucket plus(Bucket other) { //1
return new Bucket(this.size + other.size)
}
}
//1: Bucket 類實現了一個名爲 plus 的特殊方法
僅僅通過實現 plus() 方法,現在 Bucket 類就可以像下面這樣使用加法操作符:
def b1 = new Bucket(4)
def b2 = new Bucket(11)
assert (b1 + b2).size == 15 //1
//1: 可以使用加法操作符 + 對兩個 Bucket 對象進行相加
所有非比較型的 Groovy 操作符都有一個與之相關聯的方法,你可以在自己的類中按需實現這些方法。唯一的要求是,該方法要是公有的,有正確的名稱和正確個數的參數。而參數類型就依賴於你想在操作符右側支持哪些類型了。例如你可以支持下面這樣的調用:
assert (b1 + 11).size == 15
這只需要實現一個具有以下簽名的 plus() 方法即可:
Bucket plus(int capacity) {
return new Bucket(this.size + capacity)
}
下面是操作符和其關聯的方法名的完整列表:
操作符 | 關聯方法 | 操作符 | 關聯方法 |
---|---|---|---|
|
a.plus(b) |
|
a.getAt(b) |
|
a.minus(b) |
|
a.putAt(b, c) |
|
a.multiply(b) |
|
b.isCase(a) |
|
a.div(b) |
|
a.leftShift(b) |
|
a.mod(b) |
|
a.rightShift(b) |
|
a.power(b) |
|
a.rightShiftUnsigned(b) |
|
a.or(b) |
|
a.next() |
|
a.and(b) |
|
a.previous() |
|
a.xor(b) |
|
a.positive() |
|
a.asType(b) |
|
a.negative() |
|
a.call() |
|
a.bitwiseNegate() |