Groovy語言規範之程式結構
前言:
官方關於Programm Structure的介紹:Programm Structure
下文將會介紹Groovy的程式結構。
1.包名 Package names
這裡的包名同Java中的包名發揮著同樣的角色。包名允許我們分隔程式碼從而避免衝突。Groovy類必須在定義之前指定他們的包,並且假設預設的包名存在。
定義包的方式和Java非常相似
// defining a package named com.yoursite
package com.yoursite
你可以呼叫Foo類使用com.yoursite.com.Foo,當然下面也將介紹import宣告的方式引用類。
2.導包 Imports
為了引用任意一個類,而不需要包名。Groovy遵從了Java的方式允許使用import宣告來解決類的匯入。
例如,Groovy提供了幾個builder類,例如MarkupBuilder。MarkupBuilder類在groovy.xml包中,所有你可以使用該類,通過以下方式的匯入:
// 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. 預設匯入 Default imports
預設匯入時Groovy語言預設匯入的。例如下面的程式碼:
new Date()
同樣的程式碼在Java中則需要導包宣告: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. 簡單導包 Simple 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.3. 星號匯入 Star import
像Java一樣,Groovy提供了一個特殊的方式匯入包下的所有的類通過使用*,也就是所謂的星號導包。實行星號導包前的匯入方式:
import groovy.xml.MarkupBuilder
import groovy.xml.StreamingMarkupBuilder
def markupBuilder = new MarkupBuilder()
assert markupBuilder != null
assert new StreamingMarkupBuilder() != null
使用星號導包後:
import groovy.xml.*
def markupBuilder = new MarkupBuilder()
assert markupBuilder != null
assert new StreamingMarkupBuilder() != null
2.4.靜態導包 Static import
在Groovy的靜態導包允許你匯入類之後可以向靜態方法一樣在你自己的類中。和Java的靜態匯入相似,但是在Java1.4之上才能正常工作。
用法和Java基本相似,不再贅述:
import static Boolean.FALSE
assert !FALSE //use directly, without Boolean prefix!
2.5.靜態導包別名 Static import aliasing
這種用法比較類似於C語言中的typedef的用法。
使用as關鍵字的靜態導包為名稱空間問題提供了一個簡潔的解決方案。加入你想要獲得一個Calendar例項,使用getInstance()方法。它是靜態方法,所以你可以使用靜態匯入。如果採用靜態發匯入,在呼叫時將不帶類名因此容易誤導使用者。我們可以使用別名的形式進行匯入,以增強閱讀性。
import static Calendar.getInstance as now
assert now().class == Calendar.getInstance().class
這樣變得非常簡潔易讀。
2.6.靜態星號匯入 Static star import
星號靜態匯入和規則的星號匯入很相似。將會匯入該類的所有的靜態的方法。
例如,下面的例子:
import static java.lang.Math.*
assert sin(0) == 0.0
assert cos(0) == 1.0
2.7 別名匯入 Import aliasing
使用別名,我們可以使用自定義的名稱來指向一個完全的類。同上,可以通過as關鍵字來實現。
考慮以下類,假設由第三方的類庫提供的:
package thirdpartylib
public class MultiplyTwo {
def static multiply(def value) {
return value * 3 //intentionally wrong.
}
}
假如我們這樣使用這個庫:
def result = new MultiplyTwo().multiply(2)
現在假設有這樣一種情況,在使用了這個第三方庫並且貫穿了你所有的程式碼,我們發現它沒有給出一個正確的結果。我們怎樣在一處修改,而不是修改原始碼,而不改變其他使用的地方?Groovy提供了簡潔的方案。
使用簡單的別名匯入,我們可以修復這個bug像這樣:
import thirdpartylib.MultiplyTwo as OrigMultiplyTwo
class MultiplyTwo extends OrigMultiplyTwo {
def multiply(def value) {
return value * 2 // fixed here
}
}
// nothing to change below here
def multiplylib = new MultiplyTwo()
// assert passes as well
assert 4 == new MultiplyTwo().multiply(2)
這就是通過as關鍵字重新命名匯入的類解決了這個問題。
別名匯入在解決星號匯入時的命名衝突同樣有用。
3.指令碼vs類 Script versus classes
3.1. public static void main vs 指令碼 public static void main vs script
Groovy同時支援class和script(即類和指令碼)。使用一下程式碼作為例子:
Main.groovy
class Main { //定義一個Main類,名字是任意的
static void main(String... args) {
println 'Groovy world!'
}
}
你會發現以上是Java中的典型程式碼,程式碼嵌入類中執行。而Groovy是的這些變得更加容易,以下程式碼是等價的:
Main.groovy
println 'Groovy world!'
你可以把指令碼想象成是一個不用宣告的類。
3.2 指令碼類 Script class
指令碼總是會被編譯成類。Groovy編譯器會為你編譯這個類,會把指令碼體複製進run方法。上面的例子會被編譯如下:
Main.groovy
import org.codehaus.groovy.runtime.InvokerHelper
class Main extends Script { //Main類繼承了groovy.lang.Script類
def run() { //groovy.lang.Script需要run方法返回一個值
println 'Groovy world!'
}
static void main(String[] args) { //自動生成
InvokerHelper.runScript(Main, args)
}
}
如果指令碼是一個檔案,檔案的基本名稱會被用於生成指令碼類。在這個例子中,如果檔名是Main.groovy,之後指令碼類的會被命名為Main。
3.3. 方法Methods
定義方法在指令碼中是可以的,如下:
int fib(int n) {
n < 2 ? 1 : fib(n-1) + fib(n-2)
}
assert fib(10)==89
你可以將方法和程式碼混合。生成的指令碼類會將所有的方法帶入指令碼類,並且嵌入所有的腳步體到run方法中:
println 'Hello' //腳步開始處
int power(int n) { 2**n } //一個沒有指令碼體的方法
println "2^6==${power(6)}"//指令碼
以上程式碼會被轉化成:
import org.codehaus.groovy.runtime.InvokerHelper
class Main extends Script {
int power(int n) { 2** n}//power方法被複制進了自動生成的指令碼類裡面
def run() {
println 'Hello' //指令碼被複制到了這裡面
println "2^6==${power(6)}"
}
static void main(String[] args) {
InvokerHelper.runScript(Main, args)
}
}
儘管Groovy通過你的指令碼建立了一個類,不過這個類對使用者來說依然是易懂的。特別的,指令碼被編譯成位元組碼,行號被保留。這就意味著如果指令碼中出現了一個異常,異常記錄將會返回相應的原始指令碼的行號,而不是你自動生成類的行號。
3.4. 變數Variables
變數在指令碼中不需要型別宣告。這就意味著,如下指令碼:
int x = 1
int y = 2
assert x+y == 3
等同於:
x = 1
y = 2
assert x+y == 3
然而,二者之間在語義學上是不同的,表現如下:
- 如果宣告變數如同第一個例子,它是本地變數。將會被宣告在run方法,在指令碼外部不可用。特別的,這種變數在指令碼的其他方法中不可見。
- 如果變數沒有被宣告,變數會被繫結到指令碼。並且對其他方法可見,你使用指令碼與其他程式互動需要傳遞資料在指令碼和程式之間,這種宣告是非常有用的。
如果你希望變數變成一個類的欄位而不是在Binding中,你可以使用@Field annotation.