營運子
此章節涵蓋 Groovy 程式語言的運算子。
1. 算術運算子
Groovy 支援您在數學和其他程式語言(例如 Java)中發現的常見算術運算子。所有 Java 算術運算子都受支援。讓我們在以下範例中檢視它們。
1.1. 一般算術運算子
Groovy 中提供下列二元算術運算子
運算子 | 用途 | 備註 |
---|---|---|
|
加法 |
|
|
減法 |
|
|
乘法 |
|
|
除法 |
針對整數除法使用 |
|
餘數 |
|
|
次方 |
參閱有關 次方運算 的區段,以取得有關運算回傳類型的更多資訊。 |
以下是這些運算子使用方式的幾個範例
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 | 請注意使用括號將運算式括起來,以將一元減法套用至該括起來的運算式。 |
在單元算術運算子方面,++
(遞增)和 --
(遞減)運算子可用於前置和後置表示法
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 | 後置遞增會在運算式評估並指定至 b 之後遞增 a |
2 | 後置遞減會在運算式評估並指定至 d 之後遞減 c |
3 | 前置遞增會在運算式評估並指定至 f 之前遞增 e |
4 | 前置遞減會在運算式評估並指定至 h 之前遞減 g |
有關布林值的一元非運算子,請參閱 條件運算子。
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 3.0.0 起) |
|
不同(自 Groovy 3.0.0 起) |
以下是使用這些運算子進行簡單數字比較的範例
assert 1 + 2 == 3
assert 3 != 4
assert -2 < 3
assert 2 <= 2
assert 3 <= 4
assert 5 > 1
assert 5 >= -2
===
和 !==
都受支援,它們與呼叫 is()
方法相同,分別對呼叫 is()
方法取反。
import groovy.transform.EqualsAndHashCode
@EqualsAndHashCode
class Creature { String type }
def cat = new Creature(type: 'cat')
def copyCat = cat
def lion = new Creature(type: 'cat')
assert cat.equals(lion) // Java logical equality
assert cat == lion // Groovy shorthand operator
assert cat.is(copyCat) // Groovy identity
assert cat === copyCat // operator shorthand
assert cat !== lion // negated operator shorthand
3. 邏輯運算子
Groovy 提供了三個用於布林運算式的邏輯運算子
-
&&
:邏輯「且」 -
||
:邏輯「或」 -
!
:邏輯「非」
讓我們透過以下範例來說明它們
assert !false (1)
assert true && true (2)
assert true || false (3)
1 | 「非」假為真 |
2 | 真「且」真為真 |
3 | 真「或」假為真 |
3.1. 優先順序
邏輯「非」的優先順序高於邏輯「且」。
assert (!false && false) == false (1)
1 | 在此,斷言為真(因為括號中的運算式為假),因為「非」的優先順序高於「且」,因此它只套用於第一個「假」項;否則,它會套用於「且」的結果,將其轉為真,而斷言會失敗 |
邏輯「且」的優先順序高於邏輯「或」。
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 | 我們建立一個函式,只要呼叫它,就會將 called 標記設定為真 |
2 | 在第一個案例中,在重設 called 標記後,我們確認如果 || 的左運算元為真,函式不會被呼叫,因為 || 會短路評估右運算元 |
3 | 在第二個案例中,左邊運算元為 false,因此呼叫函式,這由我們的旗標現在為 true 的事實指出 |
4 | 同樣地,對於 && ,我們確認函式未呼叫具有 false 左邊運算元的函式 |
5 | 但是函式呼叫具有 true 左邊運算元的函式 |
4. 位元與位元移位運算子
4.1. 位元運算子
Groovy 提供四個位元運算子
-
&
:位元「and」 -
|
:位元「or」 -
^
:位元「xor」(獨佔「or」) -
~
:位元否定
位元運算子可套用在類型為 byte
、short
、int
、long
或 BigInteger
的引數上。如果其中一個引數為 BigInteger
,結果將為 BigInteger
類型;否則,如果其中一個引數為 long
,結果將為 long
類型;否則,結果將為 int
類型
int a = 0b00101010
assert a == 42
int b = 0b00001000
assert b == 8
assert (a & a) == a (1)
assert (a & b) == b (2)
assert (a | a) == a (3)
assert (a | b) == a (4)
int mask = 0b11111111 (5)
assert ((a ^ a) & mask) == 0b00000000 (6)
assert ((a ^ b) & mask) == 0b00100010 (7)
assert ((~a) & mask) == 0b11010101 (8)
1 | 位元 and |
2 | 位元 and 傳回共用位元 |
3 | 位元 or |
4 | 位元 or 傳回所有「1」位元 |
5 | 設定遮罩以僅檢查最後 8 個位元 |
6 | 對自我進行位元獨佔 or 傳回 0 |
7 | 位元獨佔 or |
8 | 位元否定 |
值得注意的是,原始類型的內部表示遵循 Java 語言規範。特別是,原始類型是有符號的,這表示對於位元否定,最好總是使用遮罩來僅擷取必要的位元。
在 Groovy 中,位元運算子是 可重載 的,這表示您可以為任何類型的物件定義這些運算子的行為。
4.2. 位元移位運算子
Groovy 提供三個位元移位運算子
-
<<
:左移 -
>>
:右移 -
>>>
:右移無符號
所有三個運算子都適用於左邊引數類型為 byte
、short
、int
或 long
的情況。前兩個運算子也可套用於左邊引數類型為 BigInteger
的情況。如果左邊引數為 BigInteger
,結果將為 BigInteger
類型;否則,如果左邊引數為 long
,結果將為 long
類型;否則,結果將為 int
類型
assert 12.equals(3 << 2) (1)
assert 24L.equals(3L << 3) (1)
assert 48G.equals(3G << 4) (1)
assert 4095 == -200 >>> 20
assert -1 == -200 >> 20
assert 2G == 5G >> 1
assert -3G == -5G >> 1
1 | equals 方法用於取代 == 以確認結果類型 |
在 Groovy 中,位元移位運算子是 可重載 的,這表示你可以為任何類型的物件定義這些運算子的行為。
5. 條件運算子
5.1. Not 運算子
「not」運算子以驚嘆號 (!
) 表示,並反轉底層布林表達式的結果。特別是,可以將 not
運算子與 Groovy 真值 結合使用
assert (!true) == false (1)
assert (!'foo') == false (2)
assert (!'') == true (3)
1 | true 的否定為 false |
2 | 'foo' 是非空字串,評估為 true ,因此否定會傳回 false |
3 | '' 是空字串,評估為 false ,因此否定會傳回 true |
5.2. 三元運算子
三元運算子是一個捷徑表達式,等於將 if/else 分支指定一些值給變數。
取代
if (string!=null && string.length()>0) {
result = 'Found'
} else {
result = 'Not found'
}
你可以寫
result = (string!=null && string.length()>0) ? 'Found' : 'Not found'
三元運算子也與 Groovy 真值 相容,因此你可以讓它更簡單
result = string ? 'Found' : 'Not found'
5.3. Elvis 運算子
「Elvis 運算子」是三元運算子的縮寫。一個方便使用它的情況是,如果表達式解析為 false
(如 Groovy 真值 所述),則傳回一個「合理預設」值。一個簡單的範例如下所示
displayName = user.name ? user.name : 'Anonymous' (1)
displayName = user.name ?: 'Anonymous' (2)
1 | 使用三元運算子,你必須重複你想要指定的值 |
2 | 使用 Elvis 運算子,如果值不是 false ,則會使用經過測試的值 |
使用 Elvis 運算子可以減少程式碼的冗餘,並降低重構時發生錯誤的風險,因為無需在條件和正向傳回值中複製經過測試的表達式。
5.4. Elvis 指定運算子
Groovy 3.0.0 導入了 Elvis 運算子,例如
import groovy.transform.ToString
@ToString(includePackage = false)
class Element {
String name
int atomicNumber
}
def he = new Element(name: 'Helium')
he.with {
name = name ?: 'Hydrogen' // existing Elvis operator
atomicNumber ?= 2 // new Elvis assignment shorthand
}
assert he.toString() == 'Element(Helium, 2)'
6. 物件運算子
6.1. 安全導覽運算子
安全導覽運算子用於避免 NullPointerException
。通常,當您參照物件時,您可能需要驗證它在存取物件的方法或屬性之前不是 null
。為避免這種情況,安全導覽運算子會直接傳回 null
,而不是擲回例外,如下所示
def person = Person.find { it.id == 123 } (1)
def name = person?.name (2)
assert name == null (3)
1 | find 會傳回 null 實例 |
2 | 使用 null 安全運算子可以防止 NullPointerException |
3 | 結果為 null |
6.2. 直接欄位存取運算子
通常在 Groovy 中,當您撰寫類似這樣的程式碼時
class User {
public final String name (1)
User(String name) { this.name = name}
String getName() { "Name: $name" } (2)
}
def user = new User('Bob')
assert user.name == 'Name: Bob' (3)
1 | public 欄位 name |
2 | 傳回自訂字串的 name getter |
3 | 呼叫 getter |
user.name
呼叫會觸發對同名屬性的呼叫,也就是說,在這裡,對 name
的 getter。如果您想要擷取欄位而不是呼叫 getter,您可以使用直接欄位存取運算子
assert user.@name == 'Bob' (1)
1 | 使用 .@ 會強制使用欄位而不是 getter |
6.3. 方法指標運算子
方法指標運算子 (.&
) 可用於將方法的參照儲存在變數中,以便稍後呼叫它
def str = 'example of method reference' (1)
def fun = str.&toUpperCase (2)
def upper = fun() (3)
assert upper == str.toUpperCase() (4)
1 | str 變數包含 String |
2 | 我們將 str 實例中 toUpperCase 方法的參照儲存在名為 fun 的變數中 |
3 | fun 可以像一般方法一樣被呼叫 |
4 | 我們可以檢查結果是否與我們直接在 str 上呼叫它相同 |
使用方法指標有多個優點。首先,這種方法指標的類型是 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 並傳回 String |
3 | 我們在該函式上建立一個方法指標 |
4 | 我們建立要收集描述符的元素清單 |
5 | 方法指標可以在預期 Closure 的地方使用 |
方法指標會繫結至接收器和方法名稱。引數會在執行階段解析,表示如果您有多個具有相同名稱的方法,則語法沒有不同,只有在執行階段才會解析要呼叫的適當方法
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 | 定義一個接受 String 做為引數的超載 doSomething 方法 |
2 | 定義一個接受 Integer 做為引數的超載 doSomething 方法 |
3 | 在 doSomething 上建立一個單一方法指標,不指定引數類型 |
4 | 使用具有 String 的方法指標呼叫 doSomething 的 String 版本 |
5 | 使用具有 Integer 的方法指標呼叫 doSomething 的 Integer 版本 |
為了與 Java 8 方法參考期望值對齊,在 Groovy 3 及以上版本中,您可以使用 new
作為方法名稱來取得建構函式的指標
def foo = BigInteger.&new
def fortyTwo = foo('42')
assert fortyTwo == 42G
同樣在 Groovy 3 及以上版本中,您可以取得類別的實例方法的方法指標。此方法指標會取得一個額外的參數,作為呼叫方法的接收器實例
def instanceMethod = String.&toUpperCase
assert instanceMethod('foo') == 'FOO'
為了向後相容,任何恰好具有呼叫正確參數的靜態方法,將優先於此案例的實例方法。
6.4. 方法參考運算子
Groovy 3+ 中的 Parrot 解析器支援 Java 8+ 方法參考運算子。方法參考運算子 (::
) 可用於在期望函數介面的內容中參考方法或建構函式。這與 Groovy 的方法指標運算子提供的功能有些重疊。的確,對於動態 Groovy,方法參考運算子只不過是方法指標運算子的別名。對於靜態 Groovy,運算子會產生類似於 Java 為相同內容產生的位元組碼的位元組碼。
以下腳本顯示了一些範例,突顯各種受支援的方法參考案例
import groovy.transform.CompileStatic
import static java.util.stream.Collectors.toList
@CompileStatic
void methodRefs() {
assert 6G == [1G, 2G, 3G].stream().reduce(0G, BigInteger::add) (1)
assert [4G, 5G, 6G] == [1G, 2G, 3G].stream().map(3G::add).collect(toList()) (2)
assert [1G, 2G, 3G] == [1L, 2L, 3L].stream().map(BigInteger::valueOf).collect(toList()) (3)
assert [1G, 2G, 3G] == [1L, 2L, 3L].stream().map(3G::valueOf).collect(toList()) (4)
}
methodRefs()
1 | 類別實例方法參考:add(BigInteger val) 是 BigInteger 中的實例方法 |
2 | 物件實例方法參考:add(BigInteger val) 是物件 3G 的實例方法 |
3 | 類別靜態方法參考:valueOf(long val) 是類別 BigInteger 的靜態方法 |
4 | 物件靜態方法參考:valueOf(long val) 是物件 3G 的靜態方法(有些人認為這在一般情況下是很糟糕的風格) |
以下腳本顯示了一些範例,突顯各種受支援的建構函式參考案例
@CompileStatic
void constructorRefs() {
assert [1, 2, 3] == ['1', '2', '3'].stream().map(Integer::valueOf).collect(toList()) (1)
def result = [1, 2, 3].stream().toArray(Integer[]::new) (2)
assert result instanceof Integer[]
assert result.toString() == '[1, 2, 3]'
}
constructorRefs()
1 | 類別建構函式參考 |
2 | 陣列建構函式參考 |
7. 正規表示式運算子
7.1. 模式運算子
模式運算子 (~
) 提供建立 java.util.regex.Pattern
實例的簡單方法
def p = ~/foo/
assert p instanceof Pattern
一般來說,您會在斜線字串中找到帶有表達式的模式運算子,它可以在 Groovy 中與任何類型的 String
一起使用
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 變數建立比對器,使用右側的模式 |
2 | =~ 的回傳類型是 Matcher |
3 | 等同於呼叫 if (!m.find(0)) |
由於 Matcher
會透過呼叫其 find
方法強制轉換為 boolean
,因此 =~
運算子與 Perl 的 =~
運算子的簡單使用方式一致,當它顯示為謂詞時(在 if
、?:
等)。當目的是反覆運算指定模式的比對(在 while
等)時,請直接在比對器上呼叫 find()
或使用 iterator
DGM。
7.3. 比對運算子
比對運算子 (==~
) 是尋找運算子的輕微變異,它不會回傳 Matcher
,而是回傳布林值,並且需要輸入字串的嚴格比對
m = text ==~ /match/ (1)
assert m instanceof Boolean (2)
if (m) { (3)
throw new RuntimeException("Should not reach that point!")
}
1 | ==~ 會使用正規表示法比對主旨,但比對必須嚴格 |
2 | 因此,==~ 的回傳類型是 boolean |
3 | 等同於呼叫 if (text ==~ /match/) |
7.4. 比較尋找與比對運算子
通常,當模式涉及單一精確比對時,會使用比對運算子,否則尋找運算子可能更有用。
assert 'two words' ==~ /\S+\s+\S+/
assert 'two words' ==~ /^\S+\s+\S+$/ (1)
assert !(' leading space' ==~ /\S+\s+\S+/) (2)
def m1 = 'two words' =~ /^\S+\s+\S+$/
assert m1.size() == 1 (3)
def m2 = 'now three words' =~ /^\S+\s+\S+$/ (4)
assert m2.size() == 0 (5)
def m3 = 'now three words' =~ /\S+\s+\S+/
assert m3.size() == 1 (6)
assert m3[0] == 'now three'
def m4 = ' leading space' =~ /\S+\s+\S+/
assert m4.size() == 1 (7)
assert m4[0] == 'leading space'
def m5 = 'and with four words' =~ /\S+\s+\S+/
assert m5.size() == 2 (8)
assert m5[0] == 'and with'
assert m5[1] == 'four words'
1 | 等同,但建議不要使用明確的 ^ 和 $,因為它們並不需要 |
2 | 沒有比對,因為有前導空白 |
3 | 一個比對 |
4 | ^ 和 $ 表示需要精確比對 |
5 | 零個比對 |
6 | 一個比對,貪婪地從第一個字開始 |
7 | 一個比對,忽略前導空白 |
8 | 兩個比對 |
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 項目的集合 |
表達式 cars*.make
等同於 cars.collect{ it.make }
。Groovy 的 GPath 標記法允許在引用的屬性不是包含清單的屬性時使用捷徑,在這種情況下,它會自動展開。在先前提到的情況中,可以使用表達式 cars.make
,儘管建議保留明確的展開點運算子。
展開運算子是空值安全的,表示如果集合的元素為空值,它會傳回空值,而不是擲出 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 | 使用展開運算子不會擲出 NullPointerException |
3 | 接收器也可能是空值,在這種情況下,傳回值為 null |
展開運算子可以在任何實作 Iterable
介面的類別上使用
class Component {
Integer 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'] // flatten one level
assert models.flatten() == ['408', '508', 'Clio', 'Captur'] // flatten all levels (one in this case)
考慮對集合的集合使用 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 包含所有預期的鍵,但 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).collect() == [1, 2, 3, 4, 5] (4)
assert (0<..<5).collect() == [1, 2, 3, 4] (5)
assert (0..5) instanceof List (6)
assert (0..5).size() == 6 (7)
1 | 儲存在區域變數中的簡單整數範圍 |
2 | IntRange ,包含下限 |
3 | IntRange ,包含上限 |
4 | IntRange ,包含下限 |
5 | IntRange ,包含下限和上限 |
6 | groovy.lang.Range 實作 List 介面 |
7 | 表示您可以呼叫其上的 size 方法 |
範圍實作很輕量,表示僅儲存下限和上限。您可以從任何具有 next()
和 previous()
方法來確定範圍中下一個/上一個項目的 Comparable
物件建立範圍。例如,您可以這樣建立字元範圍
assert ('a'..'d').collect() == ['a','b','c','d']
8.3. 太空船運算子
太空船運算子 (<=>
) 委派給 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 也支援範圍 |
4 | putAt 也是如此 |
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 | 建立範例使用者 |
4 | 使用索引 0 的索引運算子允許擷取使用者 ID |
5 | 使用索引 1 的索引運算子允許擷取使用者名稱 |
6 | 由於委派給 putAt ,我們可以使用索引運算子寫入屬性 |
7 | 並檢查它是否真的是已變更的屬性 name |
8.5. 安全索引運算子
Groovy 3.0.0 導入安全索引運算子,即 ?[]
,類似於 ?.
。例如
String[] array = ['a', 'b']
assert 'b' == array?[1] // get using normal array index
array?[1] = 'c' // set using normal array index
assert 'c' == array?[1]
array = null
assert null == array?[1] // return null for all index values
array?[1] = 'c' // quietly ignore attempt to set value
assert null == array?[1]
def personInfo = [name: 'Daniel.Sun', location: 'Shanghai']
assert 'Daniel.Sun' == personInfo?['name'] // get using normal map index
personInfo?['name'] = 'sunlan' // set using normal map index
assert 'sunlan' == personInfo?['name']
personInfo = null
assert null == personInfo?['name'] // return null for all map values
personInfo?['name'] = 'sunlan' // quietly ignore attempt to set value
assert null == personInfo?['name']
8.6. 成員運算子
成員運算子 (in
) 等同於呼叫 isCase
方法。在 List
的上下文中,它等同於呼叫 contains
,如下例所示
def list = ['Grace','Rob','Emmy']
assert ('Emmy' in list) (1)
assert ('Alex' !in list) (2)
1 | 等同於呼叫 list.contains('Emmy') 或 list.isCase('Emmy') |
2 | 成員否定等同於呼叫 !list.contains('Emmy') 或 !list.isCase('Emmy') |
8.7. 身分運算子
在 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)
assert list1 !== list2 (5)
1 | 建立一個字串清單 |
2 | 建立另一個包含相同元素的字串清單 |
3 | 使用 == ,我們測試物件相等性,等同於 Java 中的 list1.equals(list2) |
4 | 使用 is ,我們可以檢查參考是否不同,等同於 Java 中的 list1 == list2 |
5 | 使用 === 或 !== (自 Groovy 3.0.0 起支援且建議使用),我們也可以檢查參考是否不同,等同於 Java 中的 list1 == list2 和 list1 != list2 |
8.8. 強制轉換運算子
強制轉換運算子 (as
) 是轉型的變體。強制轉換將物件從一種型別轉換為另一種型別,而不讓它們相容於指派。我們舉個例子
String input = '42'
Integer num = (Integer) input (1)
1 | String 無法指派給 Integer ,因此它會在執行階段產生 ClassCastException |
可以使用強制轉換來修正這個問題
String input = '42'
Integer num = input as Integer (1)
1 | String 無法指派給 Integer ,但使用 as 會將它強制轉換為 Integer |
當一個物件被強制轉換為另一個物件時,除非目標型別與來源型別相同,否則強制轉換會傳回一個新的物件。強制轉換的規則會根據來源和目標型別而有所不同,如果找不到轉換規則,強制轉換可能會失敗。可以透過 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.9. 菱形運算子
菱形運算子 (<>
) 是語法糖,僅加入以支援與 Java 7 中同名運算子的相容性。用於指示應從宣告中推論泛型類型
List<String> strings = new LinkedList<>()
在動態 Groovy 中,這完全沒有使用。在靜態類型檢查的 Groovy 中,這也是可選的,因為 Groovy 類型檢查器會執行類型推論,無論此運算子是否存在。
8.10. 呼叫運算子
呼叫運算子 ()
用於隱式呼叫名為 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 | 我們可以使用傳統的方法呼叫語法來呼叫方法 |
3 | 或者,我們可以省略 .call ,感謝呼叫運算子 |
9. 運算子優先順序
下表列出所有 groovy 運算子,依優先順序排列。
等級 | 運算子 | 名稱 |
---|---|---|
1 |
|
物件建立,明確的括號 |
|
方法呼叫,閉包,文字清單/映射 |
|
|
成員存取,方法閉包,欄位/屬性存取 |
|
|
安全解除參考,展開,展開點,展開映射 |
|
|
按位元否定/模式,非,類型轉換 |
|
|
清單/映射/陣列(安全)索引,後置遞增/遞減 |
|
2 |
|
次方 |
3 |
|
前置遞增/遞減,單元加號,單元減號 |
4 |
|
乘法,除法,餘數 |
5 |
|
加法,減法 |
6 |
|
左/右(無符號)位移,包含/排除範圍 |
7 |
|
小於/大於/或等於,在,不在,實例,非實例,類型強制轉換 |
8 |
|
等於,不等於,比較,相同,不相同 |
|
regex 尋找、regex 比對 |
|
9 |
|
二進制/按位元與 |
10 |
|
二進制/按位元異或 |
11 |
|
二進制/按位元或 |
12 |
|
邏輯與 |
13 |
|
邏輯或 |
14 |
|
三元條件式 |
|
Elvis 運算子 |
|
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() |