營運子

此章節涵蓋 Groovy 程式語言的運算子。

1. 算術運算子

Groovy 支援您在數學和其他程式語言(例如 Java)中發現的常見算術運算子。所有 Java 算術運算子都受支援。讓我們在以下範例中檢視它們。

1.1. 一般算術運算子

Groovy 中提供下列二元算術運算子

運算子 用途 備註

+

加法

-

減法

*

乘法

/

除法

針對整數除法使用 intdiv(),並參閱有關 整數除法 的區段,以取得有關除法回傳類型的更多資訊。

%

餘數

**

次方

參閱有關 次方運算 的區段,以取得有關運算回傳類型的更多資訊。

以下是這些運算子使用方式的幾個範例

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」)

  • ~:位元否定

位元運算子可套用在類型為 byteshortintlongBigInteger 的引數上。如果其中一個引數為 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 提供三個位元移位運算子

  • <<:左移

  • >>:右移

  • >>>:右移無符號

所有三個運算子都適用於左邊引數類型為 byteshortintlong 的情況。前兩個運算子也可套用於左邊引數類型為 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 的方法指標呼叫 doSomethingString 版本
5 使用具有 Integer 的方法指標呼叫 doSomethingInteger 版本

為了與 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. 索引運算子

索引運算子是 getAtputAt 的簡寫符號,具體取決於您是在指定位置的左側還是右側找到它

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 == list2list1 != 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 類別定義了一個從 UserIdentifiable 的自訂轉換規則
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

new   ()

物件建立,明確的括號

()   {}   []

方法呼叫,閉包,文字清單/映射

.   .&   .@

成員存取,方法閉包,欄位/屬性存取

?.   *   *.   *:

安全解除參考,展開,展開點,展開映射

~   !   (type)

按位元否定/模式,非,類型轉換

[]   ?[]   ++   --

清單/映射/陣列(安全)索引,後置遞增/遞減

2

**

次方

3

+` {nbsp} `--` {nbsp} `   -

前置遞增/遞減,單元加號,單元減號

4

*   /   %

乘法,除法,餘數

5

+   -

加法,減法

6

<<   >>   >>>   ..   ..<   <..<   <..

左/右(無符號)位移,包含/排除範圍

7

<   <=   >   >=   in   !in   instanceof   !instanceof   as

小於/大於/或等於,在,不在,實例,非實例,類型強制轉換

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[b]

a.getAt(b)

-

a.minus(b)

a[b] = c

a.putAt(b, c)

*

a.multiply(b)

a in 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

a.positive()

as

a.asType(b)

-a

a.negative()

a()

a.call()

~a

a.bitwiseNegate()