1. 程式人生 > >Groovy語言規範之程式結構

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.