鍍金池/ 教程/ Java/ 與 Java 的區(qū)別
Grape 依賴管理器
與 Java 的區(qū)別
語法風格指南
Groovy 開發(fā)工具包
領域專用語言
安全更新
Groovy 與應用的集成
運行時及編譯時元編程(end)
測試指南
安裝 Groovy
設計模式
Groovy 的下載

與 Java 的區(qū)別

Groovy 試圖盡可能地讓 Java 開發(fā)者快速適應。在設計 Groovy 時,我們努力不讓用戶感到驚訝,即遵循“最小驚訝”原則,特別是針對那些此前有 Java 開發(fā)背景的 Groovy 初學者。

下面講講 Groovy 與 Java 的主要不同點。

1. 默認導入

下面這些包和類都是默認導入的,也就是說,不用再顯式地使用 import 語句了:

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

2. 多重方法

在 Groovy 中,調用的方法將在運行時被選擇。這種機制被稱為運行時分派或多重方法(multi-methods),是根據運行時實參(argument)的類型來選擇方法。Java 采用的是相反的策略:編譯時根據聲明的類型來選擇方法。

下面的 Java 代碼可以用 Java 和 Groovy 來編譯,但兩種編譯結果截然不同:

int method(String arg) {
    return 1;
}
int method(Object arg) {
    return 2;
}
Object o = "Object";
int result = method(o);

用 Java 編譯的結果如下:

assertEquals(2, result);

用 Groovy 編譯的結果則為:

assertEquals(1, result);

產生差異的原因在于,Java 使用靜態(tài)數(shù)據類型,o 被聲明為 Object 對象,而 Groovy 會在運行時實際調用方法時進行選擇。因為調用的是 String 類型的對象,所以自然調用 String 版本的方法。

3. 數(shù)組初始化表達式

在 Groovy 中,{...} 語句塊是留給閉包(Closure)使用的,這意味著你不能使用以下這種格式來創(chuàng)建數(shù)組:

int[] array = { 1, 2, 3}

正確的方式是這樣的:

int[] array = [1,2,3]

4. 包范圍可見性

在 Groovy 中,如果某個字段缺失了修飾符,并不會導致像在 Java 中那樣形成包的私有字段:

class Person {
    String name
}  

相反,它會用來創(chuàng)建一個屬性property),也就是一個私有字段private field),以及一個關聯(lián)的 getter 和一個關聯(lián)的 setter

在 Groovy 中創(chuàng)建包私有字段,可以通過標注 @PackageScope 來實現(xiàn)。

class Person {
    @PackageScope String name
}

5. ARM 語句塊

ARM(Automatic Resource Management,自動資源管理)語句塊從 Java 7 開始引入,但 Groovy 并不支持。相反,Groovy 提供多種基于閉包的方法,效果相同但卻合乎習慣。例如:

Path file = Paths.get("/path/to/file");
Charset charset = Charset.forName("UTF-8");
try (BufferedReader reader = Files.newBufferedReader(file, charset)) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }

} catch (IOException e) {
    e.printStackTrace();
}

可以寫成下面這樣的代碼:

new File('/path/to/file').eachLine('UTF-8') {
   println it
}

或者,如果你想讓它更接近于 Java 的慣用形式,可以這樣寫:

new File('/path/to/file').withReader('UTF-8') { reader ->
   reader.eachLine {
       println it
   }
}

6. 內部類

Groovy 中的匿名內部類和內嵌類的實現(xiàn)跟 Java 是一樣的,但你不應拿 Java 語言規(guī)范來考量它,應對差異情況保持冷靜與寬容。已完成的實現(xiàn)看起來有點類似 groovy.lang.Closure 類的實現(xiàn)。一方面,這使得訪問私有字段和方法可能會比較麻煩,但另一方面,本地變量也不必非要設置成 final 了。

6.1 靜態(tài)內部類

下面是一個靜態(tài)內部類的例子:

class A {
    static class B {}
}

new A.B()

靜態(tài)內部類是最受支持的,所以如果你確實需要一個內部類,應該首先考慮靜態(tài)內部類。

6.2 匿名內部類

import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

CountDownLatch called = new CountDownLatch(1)

Timer timer = new Timer()
timer.schedule(new TimerTask() {
    void run() {
        called.countDown()
    }
}, 0)

assert called.await(10, TimeUnit.SECONDS)

6.3 創(chuàng)建非靜態(tài)內部類的實例

在 Java 中,你可以這樣做:

public class Y {
    public class X {}
    public X foo() {
        return new X();
    }
    public static X createX(Y y) {
        return y.new X();
    }
}

而 Groovy 不支持 y.new X(),需要使用 new X(y),如下所示:

public class Y {
    public class X {}
    public X foo() {
        return new X()
    }
    public static X createX(Y y) {
        return new X(y)
    }
}

然而,要注意的是,在 Groovy 中,調用的方法可以有形參而沒有實參。在這種情況下,形參可能取 null。調用構造函數(shù)通常也適用于此規(guī)則。不過危險的是,以上述代碼為例,你必須寫為 new X(),而不是new X(this)。這可能也是一種處理該問題的常規(guī)方法,目前對此也沒有更好的解決辦法。

7. Lambda 表達式

Java 8 支持 Lambda 表達式和方法引用:

Runnable run = () -> System.out.println("Run");
list.forEach(System.out::println);  

Java 8 的 lambda 幾乎可以認為是匿名內部函數(shù)。Groovy 不支持這種格式,而采用閉包來實現(xiàn)。

Runnable run = { println 'run' }
list.each { println it } // or list.each(this.&println)

8. GString

由于雙引號所包括起來的字符串字面量會被解釋為 GString 值(即 “Groovy 字符串”的簡稱),所以如果當某個類中的 String 字面量含有美元字符($)時,那么利用 Groovy 和 Java 編譯器進行編譯時,Groovy 很可能就會編譯失敗,或者產生與 Java 編譯所不同的結果。

通常,如果某個 API 聲明了形參的類型,Groovy 會自動轉換 GStringString。要小心那些形參為 Object 的 Java API,需要檢查它們的實際類型。

9. 字符串和字符字面量

在 Groovy 中,由單引號所創(chuàng)建的字面量屬于 String 類型對象,而雙引號創(chuàng)建的字面量則可能是 StringGString 對象,具體分類由字面量中是否有插值來決定。

assert 'c'.getClass()==String
assert "c".getClass()==String
assert "c${1}".getClass() in GString  

當把一個包含單個字符的 String 對象賦予一個 char 類型變量時,Groovy 會自動將該對象轉換為 char 類型。在調用帶有 char 類型實參的方法時,我們需要顯式地轉換參數(shù)值,或者確認參數(shù)值已經預先轉換過了。

char a='a'
assert Character.digit(a, 16)==10 : 'But Groovy does boxing'
assert Character.digit((char) 'a', 16)==10

try {
  assert Character.digit('a', 16)==10
  assert false: 'Need explicit cast'
} catch(MissingMethodException e) {
}

Groovy 支持兩種轉換模式。在把一個多字符的字符串轉換為 char 類型對象時,兩種模式的轉換結果會有微小的差別:Groovy 模式的轉換更寬容一些,只取第一個字符;C 模式的轉換則出現(xiàn)異常錯誤,從而失敗。

// 對于僅包含單個字符的字符串,兩種轉換模式是等效的   
assert ((char) "c").class==Character
assert ("c" as char).class==Character

// 對于包含多個字符的字符串來說,兩種模式的結果并不相同 
try {
  ((char) 'cx') == 'c'
  assert false: 'will fail - not castable'
} catch(GroovyCastException e) {
}
assert ('cx' as char) == 'c'
assert 'cx'.asType(char) == 'c'

10.

11. == 的行為差異

在 Java 中,== 代表基本數(shù)據類型的相同,或對象引用的等價性。在 Groovy 中,== 的含義變成了 a.compareTo(b)==0,不過這要當且僅當 == 兩邊的對象都實現(xiàn)了 Comparable 接口時才能實現(xiàn),否則 == 就等同于 a.equals(b)。而要想在 Groovy 中檢查對象間的引用等價性,則需使用 is,比如:a.is(b)。

12. 轉換

Java 能夠自動執(zhí)行一些擴寬轉換和縮減轉換,關于轉換的概念參見這里。

表1 Java 轉換

轉換目的類型
轉換源類型 boolean byte short char int long float double
boolean - N N N N N N N
byte N - Y C Y Y Y Y
short N C - C Y Y Y Y
char N C C - Y Y Y Y
int N C C C - Y T Y
long N C C C C - T T
float N C C C C C - Y
double N C C C C C C -

* Y 表示 Java 可以執(zhí)行的轉換。C 表示的是在顯式轉換時 Java 能夠執(zhí)行的轉換。T 表示 Java 可以執(zhí)行的轉換,但數(shù)據被截斷了。N 表示 Java 所不能實行的轉換。

Groovy 大大擴展這些轉換。

表2 Groovy 轉換

轉換目的類型
轉換源類型 boolean Boolean byte Byte short Short char Character int Integer long Long BigInteger float Float double Double BigDecimal
boolean - B N N N N N N N N N N N N N N N N
Boolean B - N N N N N N N N N N N N N N N N
byte T T - B Y Y Y D Y Y Y Y Y Y Y Y Y Y
Byte T T B - Y Y Y D Y Y Y Y Y Y Y Y Y Y
short T T D D - B Y D Y Y Y Y Y Y Y Y Y Y
Short T T D T B - Y D Y Y Y Y Y Y Y Y Y Y
char T T Y D Y D - D Y D Y D D Y D Y D D
Character T T D D D D D - D D D D D D D D D D
int T T D D D D Y D - B Y Y Y Y Y Y Y Y
Integer T T D D D D Y D B - Y Y Y Y Y Y Y Y
long T T D D D D Y D D D - B Y T T T T Y
Long T T D D D T Y D D T B - Y T T T T Y
BigInteger T T D D D D D D D D D D - D D D D T
float T T D D D D T D D D D D D - B Y Y Y
Float T T D T D T T D D T D T D B - Y Y Y
double T T D D D D T D D D D D D D D - B Y
Double T T D T D T T D D T D T D D T B - Y
BigDecimal T T D D D D D D D D D D D T D T D -

* Y 表示 Groovy 可以執(zhí)行的轉換。D 表示的是在動態(tài)編譯或顯式轉換時 Groovy 能夠執(zhí)行的轉換。T 表示 Groovy 可以執(zhí)行的轉換,但數(shù)據被截斷了。B 表示裝箱/拆箱操作。N 表示 Groovy 不能實行的轉換。

在轉換為 boolean/Boolean 時,截斷使用的是 Groovy Truth。從數(shù)值轉換為字符是將 Number.intvalue() 轉換為 char。從 FloatDouble 轉換時,Groovy 使用 Number.doubleValue() 來構建 BigIntegerBigDecimal,否則將使用 toString() 來構建。其他轉換則有 java.lang.Number 所定義的行為。

13. 額外的關鍵字

Groovy 的關鍵字要比 Java 中的多一些。不要使用以下這些關鍵字來定義變量名等名稱:

  • as
  • def
  • in
  • trait

14. default 必須位于 swith / case 結構的結尾

default 必須位于 swith / case 結構的結尾。在 Java 中,它可以放在 swith / case 結構中的任何位置,但在 Groovy 中,它更像是一個 else 子句,而非一個默認的 case 子句。

上一篇:領域專用語言