程式結構
本章節介紹 Groovy 程式語言的程式結構。
1. 套件名稱
套件名稱在 Groovy 中的作用與在 Java 中完全相同。它們允許我們在沒有任何衝突的情況下區分程式碼庫。Groovy 類別必須在類別定義之前指定其套件,否則會假設使用預設套件。
定義套件與 Java 非常相似
// defining a package named com.yoursite
package com.yoursite
若要參考 com.yoursite.com
套件中的 Foo
類別,您需要使用完全限定名稱 com.yoursite.com.Foo
,或者您可以使用 import
陳述式,如下所示。
2. 匯入
要參照任何類別,您需要其套件的合格參照。Groovy 遵循 Java 的概念,允許使用 import
陳述式來解析類別參照。
例如,Groovy 提供了多個建構器類別,例如 MarkupBuilder
。MarkupBuilder
位於套件 groovy.xml
中,因此要使用此類別,您需要像所示範的那樣 import
它
// importing the class MarkupBuilder
import groovy.xml.MarkupBuilder
// using the imported class to create an object
def xml = new MarkupBuilder()
assert xml != null
2.1. 預設匯入
預設匯入是 Groovy 語言預設提供的匯入。例如,請看以下程式碼
new Date()
Java 中的相同程式碼需要一個匯入陳述式來匯入 Date
類別,如下所示:import java.util.Date。Groovy 預設會為您匯入這些類別。
Groovy 會為您新增以下匯入
import java.lang.*
import java.util.*
import java.io.*
import java.net.*
import groovy.lang.*
import groovy.util.*
import java.math.BigInteger
import java.math.BigDecimal
這樣做是因為這些套件中的類別是最常使用的。透過匯入,可以減少這些樣板程式碼。
2.2. 簡單匯入
簡單匯入是一個匯入陳述式,您在其中完整定義類別名稱和套件。例如,以下程式碼中的匯入陳述式 import groovy.xml.MarkupBuilder 是簡單匯入,它直接參照套件中的類別。
// importing the class MarkupBuilder
import groovy.xml.MarkupBuilder
// using the imported class to create an object
def xml = new MarkupBuilder()
assert xml != null
2.3. 星號匯入
Groovy 與 Java 一樣,提供了一個特殊方式,可以使用 *
(稱為星號匯入)匯入套件中的所有類別。MarkupBuilder
是位於套件 groovy.xml
中的類別,與另一個稱為 StreamingMarkupBuilder
的類別並列。如果您需要使用這兩個類別,您可以執行
import groovy.xml.MarkupBuilder
import groovy.xml.StreamingMarkupBuilder
def markupBuilder = new MarkupBuilder()
assert markupBuilder != null
assert new StreamingMarkupBuilder() != null
這是完全有效的程式碼。但是透過星號匯入,我們可以用一行就達到相同的效果。星號會匯入套件 groovy.xml
下的所有類別
import groovy.xml.*
def markupBuilder = new MarkupBuilder()
assert markupBuilder != null
assert new StreamingMarkupBuilder() != null
星號匯入的一個問題是它們會混亂您的本機名稱空間。但是透過 Groovy 提供的別名類型,可以輕鬆解決此問題。
2.4. 靜態匯入
Groovy 的靜態匯入功能讓您可以參照匯入的類別,就像它們是您自己的類別中的靜態方法一樣
import static Boolean.FALSE
assert !FALSE //use directly, without Boolean prefix!
這類似於 Java 的靜態匯入功能,但比 Java 更動態,因為它允許您定義與匯入方法同名的方法,只要您有不同的類型
import static java.lang.String.format (1)
class SomeClass {
String format(Integer i) { (2)
i.toString()
}
static void main(String[] args) {
assert format('String') == 'String' (3)
assert new SomeClass().format(Integer.valueOf(1)) == '1'
}
}
1 | 方法的靜態匯入 |
2 | 宣告方法,其名稱與上方靜態匯入的方法相同,但參數類型不同 |
3 | Java 中的編譯錯誤,但這是有效的 Groovy 程式碼 |
如果您有相同的類型,則匯入的類別優先。
2.5. 靜態匯入別名
使用 as
關鍵字的靜態匯入提供了名稱空間問題的優雅解決方案。假設您要取得 Calendar
執行個體,使用其 getInstance()
方法。它是一個靜態方法,因此我們可以使用靜態匯入。但是,我們不會每次都呼叫 getInstance()
,當它與其類別名稱分開時,可能會令人誤解,我們可以使用別名匯入它,以增加程式碼的可讀性
import static Calendar.getInstance as now
assert now().class == Calendar.getInstance().class
現在,這很乾淨!
2.6. 靜態星號匯入
靜態星號匯入與一般星號匯入非常類似。它會從指定的類別匯入所有靜態方法。
例如,假設我們需要為應用程式計算正弦和餘弦。類別 java.lang.Math
有名為 sin
和 cos
的靜態方法,符合我們的需求。在靜態星號匯入的協助下,我們可以執行
import static java.lang.Math.*
assert sin(0) == 0.0
assert cos(0) == 1.0
如您所見,我們可以直接存取方法 sin
和 cos
,而不需要 Math.
前綴。
2.7. 匯入別名
使用類型別名,我們可以使用我們選擇的名稱來參考完全限定的類別名稱。這可以使用 as
關鍵字來執行,如同之前一樣。
例如,我們可以將 java.sql.Date
匯入為 SQLDate
,並在與 java.util.Date
相同的檔案中使用它,而不需要使用任一類別的完全限定名稱
import java.util.Date
import java.sql.Date as SQLDate
Date utilDate = new Date(1000L)
SQLDate sqlDate = new SQLDate(1000L)
assert utilDate instanceof java.util.Date
assert sqlDate instanceof java.sql.Date
3. 腳本與類別
3.1. public static void main 與腳本
Groovy 支援腳本和類別。以以下程式碼為例
class Main { (1)
static void main(String... args) { (2)
println 'Groovy world!' (3)
}
}
1 | 定義一個 Main 類別,名稱是任意的 |
2 | public static void main(String[]) 方法可用作類別的主要方法 |
3 | 方法的主體 |
這是您會從 Java 找到的典型程式碼,其中程式碼必須嵌入到類別中才能執行。Groovy 讓它更容易,以下程式碼是等效的
println 'Groovy world!'
腳本可以被視為一個類別,而不需要宣告它,有一些差異。
3.2. 腳本類別
一個 groovy.lang.Script 始終編譯成一個類別。Groovy 編譯器會為您編譯類別,並將腳本主體複製到 run
方法中。因此,先前的範例編譯後就像下列範例一樣
import org.codehaus.groovy.runtime.InvokerHelper
class Main extends Script { (1)
def run() { (2)
println 'Groovy world!' (3)
}
static void main(String[] args) { (4)
InvokerHelper.runScript(Main, args) (5)
}
}
1 | Main 類別延伸 groovy.lang.Script 類別 |
2 | groovy.lang.Script 需要一個傳回值的 run 方法 |
3 | 腳本主體進入 run 方法 |
4 | main 方法會自動產生 |
5 | 並委派執行腳本給 run 方法 |
如果腳本在一個檔案中,則會使用該檔案的基本名稱來決定所產生腳本類別的名稱。在此範例中,如果檔案名稱是 Main.groovy
,則腳本類別會是 Main
。
3.3. 方法
可以在腳本中定義方法,如下所示
int fib(int n) {
n < 2 ? 1 : fib(n-1) + fib(n-2)
}
assert fib(10)==89
您也可以混合使用方法和程式碼。所產生的腳本類別會將所有方法載入腳本類別,並將所有腳本主體組裝到 run
方法中
println 'Hello' (1)
int power(int n) { 2**n } (2)
println "2^6==${power(6)}" (3)
1 | 腳本開始 |
2 | 在腳本主體中定義一個方法 |
3 | 並繼續執行腳本 |
此程式碼會在內部轉換成
import org.codehaus.groovy.runtime.InvokerHelper
class Main extends Script {
int power(int n) { 2** n} (1)
def run() {
println 'Hello' (2)
println "2^6==${power(6)}" (3)
}
static void main(String[] args) {
InvokerHelper.runScript(Main, args)
}
}
1 | power 方法會原封不動地複製到所產生的腳本類別中 |
2 | 第一個陳述式會複製到 run 方法中 |
3 | 第二個陳述式會複製到 run 方法中 |
即使 Groovy 從您的腳本建立一個類別,對使用者來說仍然是完全透明的。特別是,腳本會編譯成位元組碼,且會保留行號。這表示如果腳本中擲回例外,堆疊追蹤會顯示與原始腳本相符的行號,而不是我們已顯示的所產生程式碼。 |
3.4. 變數
腳本中的變數不需要類型定義。這表示此腳本
int x = 1
int y = 2
assert x+y == 3
會與下列腳本表現相同
x = 1
y = 2
assert x+y == 3
然而,這兩個腳本之間有語意上的差異
-
如果變數宣告如第一個範例,則它是一個區域變數。它會在編譯器會產生的
run
方法中宣告,且不會在腳本主體之外可見。特別是,此類變數不會在腳本的其他方法中可見 -
如果變數未宣告,則會進入 groovy.lang.Script#getBinding()。繫結從方法中可見,如果您使用腳本來與應用程式互動,且需要在腳本和應用程式之間共用資料,則繫結特別重要。讀者可以參閱 整合指南 以取得更多資訊。
讓變數對所有方法可見的另一種方法是使用 @Field 注解。使用此方式註解的變數將成為已產生腳本類別的欄位,且與區域變數一樣,存取時不會涉及腳本的 Binding 。雖然不建議這麼做,但如果你有與 binding 變數同名的區域變數或腳本欄位,你可以使用 binding.varName 存取 binding 變數。
|