執行時和編譯時超程式設計—編譯時超程式設計
執行時和編譯時超程式設計 第二部分
2 編譯時超程式設計
Groovy的編譯時超程式設計支援編譯時生成程式碼。這些變換(譯者注:原文該專有名詞是transformations,譯者直譯為變換,也許不準確。如果有知道準確翻譯的讀者懇請不吝賜教,待譯者修正)叫做程式的抽象語法樹(AST),在Groovy裡,我們叫做AST變換。AST變換支援在編譯過程中植入鉤子,修改抽象語法樹之後繼續編譯生成正常的位元組碼流。和執行時超程式設計相比,這種轉換可以在類檔案的修改可見(或者說是位元組碼流)。如果你想要你的轉換成為類協議的一部分(類協議是指實現介面,繼承抽象類等等…)甚至你需要你的類可以被Java(或其他JVM語言)呼叫,在位元組碼流的修改可見是非常重要的。比如說,一個AST轉換可以新增方法到一個類。在執行時超程式設計裡,你需要使你的新方法在Groovy裡可見,在編譯時超程式設計要實現同樣的功能,這個方法僅僅需要對Java可見即可。最後也同樣重要的是,編譯時超程式設計相比執行時超程式設計能活的更好的效能(因為沒有初始化階段)。
注:譯者也是第一次接觸Groovy,由於時間和水平有限(姑且讓譯者使用這個理由吧,對待知識本應該一絲不苟)部分專有名詞可能翻譯不準確甚至有誤(讀者閱讀的過程中最好能參考原文),懇請讀者不吝留言指出,謝謝!
在這個章節裡,我們基於Groovy釋出版本為基礎,闡述各種編譯時轉換。我們將會說明如何實現你自己的AST轉換以及這種技術的侷限性。
2.1 AST轉換能做什麼
Groovy有各種各樣的AST轉換來滿足不同的需求:刪除模板(譯者注:原文是reducing boilerplate)(程式碼生成),實現設計模式(委託,…),日誌,同步,克隆,安全指令碼,tweaking the compilation(譯者實在不知道怎麼翻譯這句,直譯太彆扭),實現Swing模式,測試程式碼以及平臺無關等。如果沒有任何一種轉換能滿足你的需求,你可以實現自己的轉換。詳細內容可以在開發自己的
AST轉換可以分為兩大類:
- 全域性AST轉換應用於顯示的,全域性的轉換。只要它們能在類路徑下被找到
- 本地AST轉換是使用註解在原始碼標註。不像全域性AST轉換,本地AST轉換支援引數。
Groovy並沒有提供全域性AST轉換,但是你可以在這裡找到你程式碼可能需要的很多本地AST轉換。
2.1.1 程式碼生成轉換
這種型別的轉換包括AST轉換可以減少模板程式碼。也就是那些你不得不寫但又沒有多少有用的資訊的程式碼。通過自動生成這種模板程式碼,需要你寫的程式碼就比較乾淨,通過連線這些木匾程式碼從而可以減少發生錯誤的可能性。
@groovy.transform.ToString
@ToString AST轉換生成一個類的可讀的toString方法。比如說,Person類里加這個註解將會自動生成toString方法:
import groovy.transform.ToString @ToString class Person { String firstName String lastName }
使用上面的類,下面的斷言意味著toString方法將會從一個類裡獲取其欄位值並將其打印出來:
def p = new Person(firstName: 'Jack', lastName: 'Nicholson') assert p.toString() == 'Person(Jack, Nicholson)'
@ToString註解可以傳入幾個引數,總結如下表:
引數 | 預設值 | 描述 | 示例 |
---|---|---|---|
IncludeNames | false | 生成的toString是否包括屬性名 |
@ToString(includeNames=true) class Person { String firstName String lastName } def p = new Person(firstName: 'Jack', lastName: 'Nicholson') assert p.toString() == 'Person(firstName:Jack, lastName:Nicholson)' |
Excludes | 空列表(EmptyList) | toString裡需要排除的屬性列表 |
@ToString(excludes=['firstName']) class Person { String firstName String lastName } def p = new Person(firstName: 'Jack', lastName: 'Nicholson') assert p.toString() == 'Person(Nicholson)' |
Includes | 空列表 | toString裡需要包括的域 |
@ToString(includes=['lastName']) class Person { String firstName String lastName } def p = new Person(firstName: 'Jack', lastName: 'Nicholson') assert p.toString() == 'Person(Nicholson)' |
includeSuper | False | 是否將超類包含到toString裡 |
@ToString class Id { long id } @ToString(includeSuper=true) class Person extends Id { String firstName String lastName } def p = new Person(id:1, firstName: 'Jack', lastName: 'Nicholson') assert p.toString() == 'Person(Jack, Nicholson, Id(1))' |
includeSuperProperties | False | 是否將超類的屬性包含到toString裡 |
class Person { String name } @ToString(includeSuperProperties = true, includeNames = true) class BandMember extends Person { String bandName } def bono = new BandMember(name:'Bono', bandName: 'U2').toString() assert bono.toString() == 'BandMember(bandName:U2, name:Bono)' |
IncludeFields | False | 是否將額外的引數也包含到toString裡 |
@ToString(includeFields=true) class Person { String firstName String lastName private int age void test() { age = 42 } } def p = new Person(firstName: 'Jack', lastName: 'Nicholson') p.test() assert p.toString() == 'Person(Jack, Nicholson, 42)' |
ignoreNulls | False | 空值是否顯示 |
@ToString(ignoreNulls=true) class Person { String firstName String lastName } def p = new Person(firstName: 'Jack') assert p.toString() == 'Person(Jack)' |
includePackage | False | toString裡是否包含全域性包名 |
@ToString(includePackage=true) class Person { String firstName String lastName } def p = new Person(firstName: 'Jack', lastName:'Nicholson') assert p.toString() == 'acme.Person(Jack, Nicholson)' |
Cache | False | 快取toString字串,如果是不可變類,應該設定為true |
@ToString(cache=true) class Person { String firstName String lastName } def p = new Person(firstName: 'Jack', lastName:'Nicholson') def s1 = p.toString() def s2 = p.toString() assert s1 == s2 assert s1 == 'Person(Jack, Nicholson)' assert s1.is(s2) // same instance |
@groovy.transform.EqualsAndHashCode
@EqualsAndHashCode AST轉換就是為了自動幫你生成equals和hashCode方法。Hashcode的生成遵循Josh Bloch在《Effective Java》描述的最佳實踐:
import groovy.transform.EqualsAndHashCode @EqualsAndHashCode class Person { String firstName String lastName } def p1 = new Person(firstName: 'Jack', lastName: 'Nicholson') def p2 = new Person(firstName: 'Jack', lastName: 'Nicholson') assert p1==p2 assert p1.hashCode() == p2.hashCode()
有數個引數可以調整@EqualsAndHashCode 的行為:
引數 | 預設值 | 描述 | 示例 |
---|---|---|---|
Excludes | Empty list | equals/hashCode方法排除掉的屬性列表 |
import groovy.transform.EqualsAndHashCode @EqualsAndHashCode(excludes=['firstName']) class Person { String firstName String lastName } def p1 = new Person(firstName: 'Jack', lastName: 'Nicholson') def p2 = new Person(firstName: 'Bob', lastName: 'Nicholson') assert p1==p2 assert p1.hashCode() == p2.hashCode() |
Includes | Empty list | equals/hashCode包含的屬性列表 |
import groovy.transform.EqualsAndHashCode @EqualsAndHashCode(includes=['lastName']) class Person { String firstName String lastName } def p1 = new Person(firstName: 'Jack', lastName: 'Nicholson') def p2 = new Person(firstName: 'Bob', lastName: 'Nicholson') assert p1==p2 assert p1.hashCode() == p2.hashCode() |
callSuper | false | 是否將超類包含到equals和hashCode計算中 |
import groovy.transform.EqualsAndHashCode @EqualsAndHashCode class Living { String race } @EqualsAndHashCode(callSuper=true) class Person extends Living { String firstName String lastName } def p1 = new Person(race:'Human', firstName: 'Jack', lastName: 'Nicholson') def p2 = new Person(race: 'Human beeing', firstName: 'Jack', lastName: 'Nicholson') assert p1!=p2 assert p1.hashCode() != p2.hashCode() |
includeFields | false | 是否將額外的屬性包含到equals/hashCode計算中 |
@ToString(includeFields=true) class Person { String firstName String lastName private int age void test() { age = 42 } } def p = new Person(firstName: 'Jack', lastName: 'Nicholson') p.test() assert p.toString() == 'Person(Jack, Nicholson, 42)' |
cache | false | 快取hashCode的計算結果,如果是不可變類,應該設定為true |
@ToString(cache=true) class Person { String firstName String lastName } def p = new Person(firstName: 'Jack', lastName:'Nicholson') def s1 = p.toString() def s2 = p.toString() assert s1 == s2 assert s1 == 'Person(Jack, Nicholson)' assert s1.is(s2) // same instance |
@groovy.transform.TupleConstructor
@TupleConstructor註解用於消除自動為你生成的建構函式模板程式碼。模板建構函式會使用每個屬性的預設值(使用Java的預設值)來生成建構函式。下面的程式碼將會生成3個建構函式:
import groovy.transform.TupleConstructor @TupleConstructor class Person { String firstName String lastName } // traditional map-style constructor def p1 = new Person(firstName: 'Jack', lastName: 'Nicholson') // generated tuple constructor def p2 = new Person('Jack', 'Nicholson') // generated tuple constructor with default value for second property def p3 = new Person('Jack')
第一個建構函式是無參建構函式,允許map-style風格構造。如果第一個屬性(或欄位)是LinkedHashMap或是單個Map就沒有用了。AbstractMap或HashMap屬性(或欄位)在map-style的對映中不可用。
其他建構函式依照他們被定義的順序進行構造。Groovy將會生成和屬性一樣多(或欄位,依據具體的引數選項)的建構函式。
@TupleConstructor AST轉換支援下面的配置選項:
引數 | 預設值 | 描述 | 示例 |
---|---|---|---|
excludes | 空列表 | 從生成的建構函式中排除的屬性 |
import groovy.transform.TupleConstructor @TupleConstructor(excludes=['lastName']) class Person { String firstName String lastName } def p1 = new Person(firstName: 'Jack', lastName: 'Nicholson') def p2 = new Person('Jack') try { // will fail because the second property is excluded def p3 = new Person('Jack', 'Nicholson') } catch (e) { assert e.message.contains ('Could not find matching constructor') } |
Includes | 空列表 | 從模板建構函式中包含的屬性列表 |
import groovy.transform.TupleConstructor @TupleConstructor(includes=['firstName']) class Person { String firstName String lastName } def p1 = new Person(firstName: 'Jack', lastName: 'Nicholson') def p2 = new Person('Jack') try { // will fail because the second property is not included def p3 = new Person('Jack', 'Nicholson') } catch (e) { assert e.message.contains ('Could not find matching constructor') } |
includeFields | False | 是否包含額外的欄位到列表 |
import groovy.transform.TupleConstructor @TupleConstructor(includeFields=true) class Person { String firstName String lastName private String occupation public String toString() { "$firstName $lastName: $occupation" } } def p1 = new Person(firstName: 'Jack', lastName: 'Nicholson', occupation: 'Actor') def p2 = new Person('Jack', 'Nicholson', 'Actor') assert p1.firstName == p2.firstName assert p1.lastName == p2.lastName assert p1.toString() == 'Jack Nicholson: Actor' assert p1.toString() == p2.toString() |
includeProperties | true | 模板建構函式是否包含全部屬性 |
import groovy.transform.TupleConstructor @TupleConstructor(includeProperties=false) class Person { String firstName String lastName } def p1 = new Person(firstName: 'Jack', lastName: 'Nicholson') try { def p2 = new Person('Jack', 'Nicholson') } catch(e) { // will fail because properties are not included } |
includeSuperFields | false | 是否包含超類的欄位到建構函式 |
import groovy.transform.TupleConstructor class Base { protected String occupation public String occupation() { this.occupation } } @TupleConstructor(includeSuperFields=true) class Person extends Base { String firstName String lastName public String toString() { "$firstName $lastName: ${occupation()}" } } def p1 = new Person(firstName: 'Jack', lastName: 'Nicholson', occupation: 'Actor') def p2 = new Person('Actor', 'Jack', 'Nicholson') assert p1.firstName == p2.firstName assert p1.lastName == p2.lastName assert p1.toString() == 'Jack Nicholson: Actor' assert p2.toString() == p1.toString() |
includeSuperProperties | true | 是否包含超類的屬性到模板建構函式 |
import groovy.transform.TupleConstructor class Base { String occupation } @TupleConstructor(includeSuperProperties=true) class Person extends Base { String firstName String lastName public String toString() { "$firstName $lastName: $occupation" } } def p1 = new Person(firstName: 'Jack', lastName: 'Nicholson') def p2 = new Person('Actor', 'Jack', 'Nicholson') assert p1.firstName == p2.firstName assert p1.lastName == p2.lastName assert p1.toString() == 'Jack Nicholson: null' assert p2.toString() == 'Jack Nicholson: Actor' |
callSuper | false | 呼叫父類建構函式時是否使用父類屬性 |
import groovy.transform.TupleConstructor class Base { String occupation Base() {} Base(String job) { occupation = job?.toLowerCase() } } @TupleConstructor(includeSuperProperties = true, callSuper=true) class Person extends Base { String firstName String lastName public String toString() { "$firstName $lastName: $occupation" } } def p1 = new Person(firstName: 'Jack', lastName: 'Nicholson') def p2 = new Person('ACTOR', 'Jack', 'Nicholson') assert p1.firstName == p2.firstName assert p1.lastName == p2.lastName assert p1.toString() == 'Jack Nicholson: null' assert p2.toString() == 'Jack Nicholson: actor' |
force | false | 預設情況下,如果建構函式已經定義,將不會有任何轉換。如果將這個引數設定為true,只要你保證不會重複定義,也會自動生成建構函式 | 參考 JavaDoc |
@groovy.transform.Canonical
@Canonical AST轉換組合了@ToString,@EqualsAndHashCode和@TupleConstructor註解的功能:
import groovy.transform.Canonical @Canonical class Person { String firstName String lastName } def p1 = new Person(firstName: 'Jack', lastName: 'Nicholson') assert p1.toString() == 'Person(Jack, Nicholson)' // Effect of @ToString def p2 = new Person('Jack','Nicholson') // Effect of @TupleConstructor assert p2.toString() == 'Person(Jack, Nicholson)' assert p1==p2 // Effect of @EqualsAndHashCode assert p1.hashCode()==p2.hashCode() // Effect of @EqualsAndHashCode
不可變類可以用類似的@Immutable AST轉換來代替。@Canonical AST轉換支援下表的配置:
引數 | 預設值 | 描述 | 示例 |
---|---|---|---|
excludes | 空列表 | 從模板建構函式排除的屬性 |
import groovy.transform.Canonical @Canonical(excludes=['lastName']) class Person { String firstName String lastName } def p1 = new Person(firstName: 'Jack', lastName: 'Nicholson') assert p1.toString() == 'Person(Jack)' // Effect of @ToString def p2 = new Person('Jack') // Effect of @TupleConstructor assert p2.toString() == 'Person(Jack)' assert p1==p2 // Effect of @EqualsAndHashCode assert p1.hashCode()==p2.hashCode() // Effect of @EqualsAndHashCode |
includes | 空列表 | 從模板建構函式包含的屬性 |
import groovy.transform.Canonical @Canonical(includes=['firstName']) class Person { String firstName String lastName } def p1 = new Person(firstName: 'Jack', lastName: 'Nicholson') assert p1.toString() == 'Person(Jack)' // Effect of @ToString def p2 = new Person('Jack') // Effect of @TupleConstructor assert p2.toString() == 'Person(Jack)' assert p1==p2 // Effect of @EqualsAndHashCode assert p1.hashCode()==p2.hashCode() // Effect of @EqualsAndHashCode |
@groovy.transform.InheritConstructors
@InheritConstructors AST 轉換的作用就是自動為你生成匹配超類建構函式的建構函式。當過載異常類時很有用:
import groovy.transform.InheritConstructors @InheritConstructors class CustomException extends Exception {} // all those are generated constructors new CustomException() new CustomException("A custom message") new CustomException("A custom message", new RuntimeException()) new CustomException(new RuntimeException()) // Java 7 only // new CustomException("A custom message", new RuntimeException(), false, true)
@InheritConstructor AST轉換支援下面的配置選項:
引數 | 預設值 | 描述 | 示例 |
---|---|---|---|
constructorAnnotations | False | 是否將建構函式註解包含進來 |
@Retention(RetentionPolicy.RUNTIME) @Target([ElementType.CONSTRUCTOR]) public @interface ConsAnno {} class Base { @ConsAnno Base() {} } @InheritConstructors(constructorAnnotations=true) class Child extends Base {} assert Child.constructors[0].annotations[0].annotationType().name == 'ConsAnno' |
parameterAnnotations | False | 是否將建構函式引數的註解拷貝進來 |
@Retention(RetentionPolicy.RUNTIME) @Target([ElementType.PARAMETER]) public @interface ParamAnno {} class Base { Base(@ParamAnno String name) {} } @InheritConstructors(parameterAnnotations=true) class Child extends Base {} assert Child.constructors[0].parameterAnnotations[0][0].annotationType().name == 'ParamAnno' |
@groovy.lang.Category
@Category AST轉換建行了Groovy類別的建立,以前,Groovy類別需要些下面的程式碼:
class TripleCategory { public static Integer triple(Integer self) { 3*self } } use (TripleCategory) { assert 9 == 3.triple() }
@Category轉換可以讓你用例項風格寫出同樣的功能,而不是使用靜態類風格。也就是說每個收到的方法不需要第一個引數了。類別可以這樣寫:
@Category(Integer) class TripleCategory { public Integer triple() { 3*this } } use (TripleCategory) { assert 9 == 3.triple() }
注意,上面的例子混合使用了this引用。在一個類別類裡使用例項欄位是沒用的。類別是沒有狀態的。
@groovy.transform.IndexProperty
@IndexedProperty註解用於為list/array型別的屬性生成索引化的getters/setters。如果你從Java轉到使用Groovy的話這一特性將會非常有用。當然Groovy支援GPath來訪問屬性,這個在Java裡不支援。@IndexedProperty註解將會生成索引化的屬性,示例如下:
class SomeBean { @IndexedProperty String[] someArray = new String[2] @IndexedProperty List someList = [] } def bean = new SomeBean() bean.setSomeArray(0, 'value') bean.setSomeList(0, 123) assert bean.someArray[0] == 'value' assert bean.someList == [123]
@groovy.lang.Lazy
@Lazy AST轉換實現延遲初始化欄位,示例如下:
class SomeBean { @Lazy LinkedList myField }
這段程式碼將會產生如下程式碼:
List $myField List getMyField() { if ($myField!=null) { return $myField } else { $myField = new LinkedList() return $myField } }
用於初始化欄位的預設值是宣告的建構函式的預設值。在屬性賦值的右邊使用閉包來賦預設值也是可以的。示例如下:
class SomeBean { @Lazy LinkedList myField = { ['a','b','c']}() }
在上面的例子裡,將會產生如下程式碼:
List $myField List getMyField() { if ($myField!=null) { return $myField } else { $myField = { ['a','b','c']}() return $myField } }
如果欄位被宣告為volatile,初始化就會使用雙重檢查鎖模式來同步。
使用soft=true引數,幫助類欄位將會使用軟引用。提供一種簡單的方式來實現快取。在這個例子裡,如果垃圾回收器決定回收引用,下次訪問該欄位時將會再一次初始化。
@groovy.lang.Newify
@Newify AST轉換用於提供可替換的語法風格到構造物件中去:
- 使用Python風格
@Newify([Tree,Leaf]) class TreeBuilder { Tree tree = Tree(Leaf('A'),Leaf('B'),Tree(Leaf('C'))) }
@Newify([Tree,Leaf]) class TreeBuilder { Tree tree = Tree.new(Leaf.new('A'),Leaf.new('B'),Tree.new(Leaf.new('C'))) }
通過將auto標誌設定為false可以禁用Ruby版本。
@groovy.transform.Sortable
@Sortable AST轉換用於幫助幫助寫Comparable的類,使其非常容易按數字大小排序。下面的程式碼提供了示例:
import groovy.transform.Sortable @Sortable class Person { String first String last Integer born }
產生的類具有一些屬性:
- 實現了Comparable介面
- 包含一個基於自然排序的compareTo方法
- 有三個方法可以返回比較器:compartorByFirst,compartorByLast和comparatorByBorn
生成的compareTo方法類似下面這樣:
public int compareTo(java.lang.Object obj) { if (this.is(obj)) { return 0 } if (!(obj instanceof Person)) { return -1 } java.lang.Integer value = this.first <=> obj.first if (value != 0) { return value } value = this.last <=> obj.last if (value != 0) { return value } value = this.born <=> obj.born if (value != 0) { return value } return 0 }
作為一個自動生成的比較器,compartorByFirst比較器將會有一個compare方法類似這樣:
public int compare(java.lang.Object arg0, java.lang.Object arg1) { if (arg0 == arg1) { return 0 } if (arg0 != null && arg1 == null) { return -1 } if (arg0 == null && arg1 != null) { return 1 } return arg0.first <=> arg1.first }
Person類可以用於任何一個需要Comparable的地方,生成的比較器如下面的例子所示:
def people = [ new Person(first: 'Johnny', last: 'Depp', born: 1963), new Person(first: 'Keira', last: 'Knightley', born: 1985), new Person(first: 'Geoffrey', last: 'Rush', born: 1951), new Person(first: 'Orlando', last: 'Bloom', born: 1977) ] assert people[0] > people[2] assert people.sort()*.last == ['Rush', 'Depp', 'Knightley', 'Bloom'] assert people.sort(false, Person.comparatorByFirst())*.first == ['Geoffrey', 'Johnny', 'Keira', 'Orlando'] assert people.sort(false, Person.comparatorByLast())*.last == ['Bloom', 'Depp', 'Knightley', 'Rush'] assert people.sort(false, Person.comparatorByBorn())*.last == ['Rush', 'Depp', 'Bloom', 'Knightley']
正常情況下,所有屬性都會用於產生compareTo方法,按照它們定義的先後順序排優先順序。你可以通過新增includes或excludes註解在給定的屬性列表前面來使compareTo方法包含或排除某些特定的屬性。如果使用includes,列表中屬性順序決定了比較時的優先順序。作為示例,考慮下面的Person類的定義:
@Sortable(includes='first,born') class Person { String last int born String first }
compareTo方法將會產生兩個比較器方法comparatorByFirst和comparatorByBorn方法:
public int compareTo(java.lang.Object obj) { if (this.is(obj)) { return 0 } if (!(obj instanceof Person)) { return -1 } java.lang.Integer value = this.first <=> obj.first if (value != 0) { return value } value = this.born <=> obj.born if (value != 0) { return value } return 0 }
Person類可以這樣用:
def people = [ new Person(first: 'Ben', last: 'Affleck', born: 1972), new Person(first: 'Ben', last: 'Stiller', born: 1965) ] assert people.sort()*.last == ['Stiller', 'Affleck']
@groovy.transform.builder.Builder
@Builder AST轉換用於幫助寫那些可用生成流API呼叫的類。這個轉換支援多種構造策略來滿足各種使用場景。同時支援許多配置選項給使用者來配置構造過程。如果你是一個AST駭客,你可以定義自己的策略類。下表列出了Groovy的全部可用測策略,每條策略都支援配置選項。
策略 | 描述 | builderClassName | builderMethodName | buildMethodNameprefix | includes/excludes |
---|---|---|---|---|---|
SimpleStrategy | chained setters | n/a | n/a | n/a | yes, default “set” yes |
ExternalStrategy | explicit builder class, class being built untouched | n/a | n/a | yes, default “build” | yes, default “” yes |
DefaultStrategy | creates a nested helper class | yes, default Builder | yes, default “builder” | yes, default “build” | yes, default “” yes |
InitializerStrategy | creates a nested helper class providing type-safe fluent creation | yes, default Initializer | yes, default “createInitializer” | yes, default “create” but usually only used internally | yes, default “” yes |
(譯者注:該表譯者也不是很明白,暫時沒有翻譯,後續研究清楚再修改,若有高人明白,懇請留言指點)
SimpleStrategy
要使用SimpleStrategy,你需要在你的Groovy類前新增@Builder註解,示例如下:
import groovy.transform.builder.* @Builder(builderStrategy=SimpleStrategy) class Person { String first String last Integer born }
然後可以使用鏈式風格呼叫setters方法:
def p1 = new Person().setFirst('Johnny').setLast('Depp').setBorn(1963) assert "$p1.first $p1.last" == 'Johnny Depp'
對於每一個屬性,自動生成的setter如下:
public Person setFirst(java.lang.String first) { this.first = first return this }
你可以指定一個特定的字首,示例如下:
import groovy.transform.builder.* @Builder(builderStrategy=SimpleStrategy, prefix="") class Person { String first String last Integer born }
鏈式風格呼叫setters如下:
def p = new Person().first('Johnny').last('Depp').born(1963) assert "$p.first $p.last" == 'Johnny Depp'
你可以配合著@Canonical註解來使用SimpleStrategy。如果你的@Builder註解沒有明確的includes或excludes註解屬性但是你的@Canonical有,則@Builder註解會重用@Canonical註解的屬性。
這個策略不支援builderClassName,builderMethodName,builderMethodName和forClass註解屬性。
Groovy已經支援有內建的building機制,如果內建的機制滿足你的需求就不要使用@Builder,示例如下:
def p2 = new Person(first: 'Keira', last: 'Knightley', born: 1985) def p3 = new Person().with { first = 'Geoffrey' last = 'Rush' born = 1951 }
ExternalStrategy
要使用ExternalStrategy,需要使用@Builder註解建立並註解一個Groovy builder類,需要使用forClass屬性指明使用ExternalStrategy策略的類。假如你有下面的一個Person類需要構建:
class Person { String first String last int born }
你建立並這樣使用你的builder:
import groovy.transform.builder.* @Builder(builderStrategy=ExternalStrategy, forClass=Person) class PersonBuilder { } def p = new PersonBuilder().first('Johnny').last('Depp').born(1963).build() assert "$p.first $p.last" == 'Johnny Depp'
注意到你提供的builder類(通常是空)將會自動生成setters。生成的程式碼如下:
public Person build() { Person _thePerson = new Person() _thePerson.first = first _thePerson.last = last _thePerson.born = born return _thePerson }
你建立的builder類可以是遵循普通JavaBean的Groovy類或Java類,比如說一個無參構造器和屬性setters方法。這有一個使用Java類的示例:
import groovy.transform.builder.* @Builder(builderStrategy=ExternalStrategy, forClass=javax.swing.DefaultButtonModel) class ButtonModelBuilder {} def model = new ButtonModelBuilder().enabled(true).pressed(true).armed(true).rollover(true).selected(true).build() assert model.isArmed() assert model.isPressed() assert model.isEnabled() assert model.isSelected() assert model.isRollover()
生成的builder可以通過prefix,includes,excludes和builderMethodName等註解屬性來自由化定製,這有一個示例:
import groovy.transform.builder.* import groovy.transform.Canonical @Canonical class Person { String first String last int born } @Builder(builderStrategy=ExternalStrategy, forClass=Person, includes=['first', 'last'], buildMethodName='create', prefix='with') class PersonBuilder { } def p = new PersonBuilder().withFirst('Johnny').withLast('Depp').create() assert "$p.first $p.last" == 'Johnny Depp'
你可以配合@Canonical註解來使用ExternalStrategy。如果你的@Builder註解沒有包含includes或excludes註解屬性但是你建立的builder的@Canonical註解聲明瞭,@Builder會自動重用@Canonical的這些屬性。
DefaultStrategy
要使用DefaultStrategy你需要在你的Groovy類前新增@Builder註解,示例如下:
import groovy.transform.builder.Builder @Builder class Person { String firstName String lastName int age } def person = Person.builder().firstName("Robert").lastName("Lewandowski").age(21).build() assert person.firstName == "Robert" assert person.lastName == "Lewandowski" assert person.age == 21
如果你願意,你可以使用builderClassName,builderMethodName,buildMethodName,prefix,includes和excludes註解屬性來個性化你的building處理過程。其中一些屬性示例如下:
import groovy.transform.builder.Builder @Builder(buildMethodName='make', builderMethodName='maker', prefix='with', excludes='age') class Person { String firstName String lastName int age } def p = Person.maker().withFirstName("Robert").withLastName("Lewandowski").make() assert "$p.firstName $p.lastName" == "Robert Lewandowski"
這個策略也支援註解靜態方法和建構函式。這種情況下,靜態方法或建構函式引數變成了要使用的building的屬性,對於靜態方法,方法的返回型別兩變成要構造的目標類。如果你在一個類(要麼是類,要麼是方法或建構函式的位置)中使用超過一個的@Builder註解,將有你來確保生成的幫助類或工廠方法名字的唯一。(比如說,不能有超過一個類使用相同的名字屬性)這有一個示例高亮了方法和建構函式的用法(同樣展示了使用唯一的命名)
import groovy.transform.builder.* import groovy.transform.* @ToString @Builder class Person { String first, last int born Person(){} @Builder(builderClassName='MovieBuilder', builderMethodName='byRoleBuilder') Person(String roleName) { if (roleName == 'Jack Sparrow') { this.first = 'Johnny'; this.last = 'Depp'; this.born = 1963 } } @Builder(builderClassName='NameBuilder', builderMethodName='nameBuilder', prefix='having', buildMethodName='fullName') static String join(String first, String last) { first + ' ' + last } @Builder(builderClassName='SplitBuilder', builderMethodName='splitBuilder') static Person split(String name, int year) { def parts = name.split(' ') new Person(first: parts[0], last: parts[1], born: year) } } assert Person.splitBuilder().name("Johnny Depp").year(1963).build().toString() == 'Person(Johnny, Depp, 1963)' assert Person.byRoleBuilder().roleName("Jack Sparrow").build().toString() == 'Person(Johnny, Depp, 1963)' assert Person.nameBuilder().havingFirst('Johnny').havingLast('Depp').fullName() == 'Johnny Depp' assert Person.builder().first("Johnny").last('Depp').born(1963).build().toString() == 'Person(Johnny, Depp, 1963)'
這個策略不支援forClass註解屬性。
InitializerStrategy
要使用InitializerStrategy,你需要在你的Groovy類前新增@Builder註解。示例如下:
import groovy.transform.builder.* import groovy.transform.* @ToString @Builder(builderStrategy=InitializerStrategy) class Person { String firstName String lastName int age }
你的類將被鎖定,這個類由一個公用的帶有全部屬性設定的初始建構函式例項化。也可以由一個工廠方法來初始化,示例如下:
@CompileStatic def firstLastAge() { assert new Person(Person.createInitializer().firstName("John").lastName("Smith").age(21)).toString() == 'Person(John, Smith, 21)' } firstLastAge()
任何沒有帶全部屬性(儘管屬性順序不重要)初始化函式將會有編譯錯誤,如果你不需要這種級別的限制,你就不要使用@CompileStatic註解。
你可以配合@Canonical和@Immutable來使用InitializerStrategy。如果你的@Builder註解沒有明確includes和excludes註解屬性但是你的@Canonical註解有,那麼@Builder註解會重用@Canonical的。這有一個使用@Builder和@Immutable註解的示例:
import groovy.transform.builder.* import groovy.transform.* @Builder(builderStrategy=InitializerStrategy) @Immutable class Person { String first String last int born } @CompileStatic def createFirstLastBorn() { def p = new Person(Person.createInitializer().first('Johnny').last('Depp').born(1963)) assert "$p.first $p.last $p.born" == 'Johnny Depp 1963' } createFirstLastBorn()
這個策略也支援註解靜態方法和建構函式。這種情況下,靜態方法或建構函式引數變成了要使用的building的屬性,對於靜態方法,方法的返回型別兩變成要構造的目標類。如果你在一個類(要麼是類,要麼是方法或建構函式的位置)中使用超過一個的@Builder註解,將有你來確保生成的幫助類或工廠方法名字的唯一。
這個策略不支援forClass註解屬性。
2.1.2 類設計註解
這個類別的註解目標是通過使用宣告風格來簡化那些著名的設計模式(委託,單例…)的實現。
@groovy.lang.Delegate
@Delegate AST轉換時為了實現委託設計模式。具體見下面的示例:
class Event { @Delegate Date when String title }
when 這個欄位宣告為了@Delegate,意味著Event類將會委託呼叫Date方法來填寫when欄位。生成的程式碼如下:
class Event { Date when String title boolean before(Date other) { when.before(other) } // ... }
然後你可以直接在Event類上呼叫before方法,示例如下:
def ev = new Event(title:'Groovy keynote', when: Date.parse('yyyy/MM/dd', '2013/09/10')) def now = new Date() assert ev.before(now)
@Delegate AST轉換的行為可以通過如下引數改變:
引數 | 預設值 | 描述 | 示例 |
---|---|---|---|
Interfaces | True | 是否介面的欄位實現類也應該繼承 |
interface Greeter { void sayHello() } class MyGreeter implements Greeter { void sayHello() { println 'Hello!'} } class DelegatingGreeter { // no explicit interface @Delegate MyGreeter greeter = new MyGreeter() } def greeter = new DelegatingGreeter() assert greeter instanceof Greeter // interface was added transparently |
deprecated | false | 如果為true,魏國方法將會註解為@Deprecated |
class WithDeprecation { @Deprecated void foo() {} } class WithoutDeprecation { @Deprecated void bar() {} } class Delegating { @Delegate(deprecated=true) WithDeprecation with = new WithDeprecation() @Delegate WithoutDeprecation without = new WithoutDeprecation() } def d = new Delegating() d.foo() // passes thanks to deprecated=true d.bar() // fails because of @Deprecated |
methodAnnotations | false | 是否將委託的方法覆蓋你的方法 |
class WithAnnotations { @Transactional void method() { } } class DelegatingWithoutAnnotations { @Delegate WithAnnotations delegate } class DelegatingWithAnnotations { @Delegate(methodAnnotations = true) WithAnnotations delegate } def d1 = new DelegatingWithoutAnnotations() def d2 = new DelegatingWithAnnotations() assert d1.class.getDeclaredMethod('method').annotations.length==0 assert d2.class.getDeclaredMethod('method').annotations.length==1 |
parameterAnnotations | false | 是否將委託方法的引數覆蓋你的引數 |
class WithAnnotations { void method(@NotNull String str) { } } class DelegatingWithoutAnnotations { @Delegate WithAnnotations delegate } class DelegatingWithAnnotations { @Delegate(parameterAnnotations = true) WithAnnotations delegate } def d1 = new DelegatingWithoutAnnotations() def d2 = new DelegatingWithAnnotations() assert d1.class.getDeclaredMethod('method',String).parameterAnnotations[0].length==0 assert d2.class.getDeclaredMethod('method',String).parameterAnnotations[0].length==1 |
excludes | 空列表 | 從委託機制排除的方法,需更加聚焦的控制,可以參考excludeTypes |
class Worker { void task1() {} void task2() {} } class Delegating { @Delegate(excludes=['task2']) Worker worker = new Worker() } def d = new Delegating() d.task1() // passes d.task2() // fails because method is excluded |
Includes | 空列表 | 從委託機制包含的方法,需更加聚焦的控制,可以參考includeTypes |
class Worker { void task1() {} void task2() {} } class Delegating { @Delegate(includes=['task1']) Worker worker = new Worker() } def d = new Delegating() d.task1() // passes d.task2() // fails because method is not included |
excludeTypes | 空列表 | 從委託機制排除掉的方法列表 |
interface AppendStringSelector { StringBuilder append(String str) } class UpperStringBuilder { @Delegate(excludeTypes=AppendStringSelector) StringBuilder sb1 = new StringBuilder() @Delegate(includeTypes=AppendStringSelector) StringBuilder sb2 = new StringBuilder() String toString() { sb1.toString() + sb2.toString().toUpperCase() } } def usb = new UpperStringBuilder() usb.append(3.5d) usb.append('hello') usb.append(true) assert usb.toString() == '3.5trueHELLO' |
includeTypes | 空列表 | 從委託機制包含的方法列表 |
interface AppendBooleanSelector { StringBuilder append(boolean b) } interface AppendFloatSelector { StringBuilder append(float b) } class NumberBooleanBuilder { @Delegate(includeTypes=AppendBooleanSelector, interfaces=false) StringBuilder nums = new StringBuilder() @Delegate(includeTypes=[AppendFloatSelector], interfaces=false) StringBuilder bools = new StringBuilder() String result() { "${nums.toString()} |
@groovy.transform.Immutable
@Immutable AST轉換簡化了建立不可變類,也就是說是用於建立那些成員變數不可變的類。示例如下:
import groovy.transform.Immutable @Immutable class Point { int x int y }
使用@Immutable宣告的不可變類自動變成了final。如果一個類是不可變的,你必須確保屬性是不可變型別(原始型別或裝箱型別),或者一個使用了@Immutable宣告的不可變類。使用@Immutable來宣告一個類的和使用@Canonical AST轉換宣告一個類非常相似,但是對於一個不可變類,會自動生成toString,equals和hashcode方法。如果你試圖去修改一個屬性將會丟擲ReadOnlyPropertyException異常。
因為@Immutable依賴於已知的不可變類(像java.net.URI或java.lang.String)如果你使用的型別不是在那個類別將會失敗,你可以使用下表的一些引數將一些型別轉為不可變類:
引數 | 預設值 | 描述 | 示例 |
---|---|---|---|
knowImmutableClass | 空列表 | 要視為不可變類的列表 |
import groovy.transform.Immutable import groovy.transform.TupleConstructor @TupleConstructor final class Point { final int x final int y public String toString() { "($x,$y)" } } @Immutable(knownImmutableClasses=[Point]) class Triangle { Point a,b,c } |
knowImmutables | 空列表 | 要視為不可變的屬性列表 |
import groovy.transform.Immutable import groovy.transform.TupleConstructor @TupleConstructor final class Point { final int x final int y public String toString() { "($x,$y)" } } @Immutable(knownImmutables=['a','b','c']) class Triangle { Point a,b,c } |
copyWith | false | 是否生成copyWith(Map)方法 |
import groovy.transform.Immutable @Immutable( copyWith=true ) class User { String name Integer age } def bob = new User( 'bob', 43 ) def alice = bob.copyWith( name:'alice' ) assert alice.name == 'alice' assert alice.age == 43 |
@groovy.transform.Memorized
@Memoized AST轉換通過支援方法呼叫結果的快取簡化了快取的實現。僅僅需要在方法前面新增@Memoized註解。想象下面的方法:
long longComputation(int seed) { // slow computation Thread.sleep(1000*seed) System.nanoTime() }
基於實際的方法引數,這個方法可能需要一段長時間的計算。沒有@Memoized,每個方法需要幾秒鐘來返回幾個隨機數。
def x = longComputation(1) def y = longComputation(1) assert x!=y
新增一個@Memoized就基於入參添加了快取從而改變了方法的語義:
@Memoized long longComputation(int seed) { // slow computation Thread.sleep(1000*seed) System.nanoTime() } def x = longComputation(1) // returns after 1 second def y = longComputation(1) // returns immediatly def z = longComputation(2) // returns after 2 seconds assert x==y assert x!=z
快取的大小可以通過兩個可選引數來配置:
- protectedCacheSize,保證不被垃圾回收器回收掉的結果大小
- maxCacheSize,可以儲存的最大快取大小
預設情況下,快取的大小沒有限制,垃圾回收器也可以回收全部快取結果。通過將protectCacheSize設定大於0將會建立一個沒有限制同時部分結果受保護的快取。將maxCacheSize設定大於0將會建立一個有限制大小但不會保護快取結果的快取。也可以同時設定兩個引數。
@groovy.lang.Singleton
@Singleton註解可以用於在一個類上實現單例模式。單例模式早期是預設被定義為懶載入模式,同時使用了雙重檢驗鎖。
@Singleton class GreetingService { String greeting(String name) { "Hello, $name!" } } assert GreetingService.instance.greeting('Bob') == 'Hello, Bob!'
預設情況下,單例被建立後可以通過instance屬性來初始化和獲取。可以通過使用property引數來改變單例模式的名字。
@Singleton(property='theOne') class GreetingService { String greeting(String name) { "Hello, $name!" } } assert GreetingService.theOne.greeting('Bob') == 'Hello, Bob!'
也可以通過加一個lazy參來實現懶載入:
class Collaborator { public static boolean init = false } @Singleton(lazy=true,strict=false) class GreetingService { static void init() {} GreetingService() { Collaborator.init = true } String greeting(String name) { "Hello, $name!" } } GreetingService.init() // make sure class is initialized assert Collaborator.init == false GreetingService.instance assert Collaborator.init == true assert GreetingService.instance.greeting('Bob') == 'Hello, Bob!'
在這個例子裡,我們設定了strict引數為false,可以允許我們自己建立自己的建構函式。
@groovy.transform.Mixin
已經廢棄了,考慮使用traits來代替。
2.1.3 日誌模組
Groovy提供了AST轉換來幫助整合最廣泛使用的日誌框架。通過使用註解可以在一個類裡新增日誌資訊。
所有的轉換工作模式都類似:
- 添加了一個static final log欄位作為logger
- 包裝呼叫log.level()到合適的log.isLevelEnable,取決於具體的框架。
這些轉換支援兩個引數
- value(預設是log)logger欄位的名字
- category(預設是類名)logger類別的名字
@groovy.util.logging.Log
第一個日誌AST轉換是@log註解。依賴於JDK的日誌框架,示例如下:
@groovy.util.logging.Log class Greeter { void greet() { log.info 'Called greeter' println 'Hello, world!' } }
也可以這樣寫:
import java.util.logging.Level import java.util.logging.Logger class Greeter { private static final Logger log = Logger.getLogger(Greeter.name) void greet() { if (log.isLoggable(Level.INFO)) { log.info 'Called greeter' } println 'Hello, world!' } }
@groovy.util.logging.Commons
Groovy通過使用@Commons註解來支援Apache Commons Logging框架,示例如下:
@groovy.util.logging.Commons class Greeter { void greet() { log.debug 'Called greeter' println 'Hello, world!' } }
這樣寫也可以:
import org.apache.commons.logging.LogFactory import org.apache.commons.logging.Log class Greeter { private static final Log log = LogFactory.getLog(Greeter) void greet() { if (log.isDebugEnabled()) { log.debug 'Called greeter' } println 'Hello, world!' } }
@groovy.util.logging.Log4j
Groovy通過@Log4j註解來支援Apache Log4j 1.x框架,示例如下:
@groovy.util.logging.Log4j class Greeter { void greet() { log.debug 'Called greeter' println 'Hello, world!' } }
等同於下面的寫法:
import org.apache.log4j.Logger class Greeter { private static final Logger log = Logger.getLogger(Greeter) void greet() { if (log.isDebugEnabled()) { log.debug 'Called greeter' } println 'Hello, world!' } }
@groovy.util.logging.Log4j2
Groovy通過@Log4j2註解來支援Apache Log4j 2.x框架。示例如下:
@groovy.util.logging.Log4j2 class Greeter { void greet() { log.debug 'Called greeter' println 'Hello, world!' } }
等同於這樣的寫法:
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger class Greeter { private static final Logger log = LogManager.getLogger(Greeter) void greet() { if (log.isDebugEnabled()) { log.debug 'Called greeter' } println 'Hello, world!' } }
@groovy.util.logging.Slf4j
@groovy.util.logging.Slf4j class Greeter { void greet() { log.debug 'Called greeter' println 'Hello, world!' } }
等同於下面的寫法:
import org.slf4j.LoggerFactory import org.slf4j.Logger class Greeter { private static final Logger log = LoggerFactory.getLogger(Greeter) void greet() { if (log.isDebugEnabled()) { log.debug 'Called greeter' } println 'Hello, world!' } }
2.1.4 宣告同步
Groovy語言提供了一系列的註解通過適當的方式來簡化常見的同步模式。
@groovy.transform.Synchronized
@Synchronized AST註解的工作模式和synchronized關鍵字類似,但是為了更加安全的同步,鎖住的不同的物件。可以用於任何方法或靜態方法:
import groovy.transform.Synchronized import java.util.concurrent.Executors import java.util.concurrent.TimeUnit class Counter { int cpt @Synchronized int incrementAndGet() { cpt++ } int get() { cpt } }
這種寫法等同於新建一個物件鎖將整個方法放到一個同步塊中:
class Counter { int cpt private final Object $lock = new Object() int incrementAndGet() { synchronized($lock) { cpt++ } } int get() { cpt } }
預設情況下,@Synchronized建立了一個$lock(對於靜態方法是$LOCK)欄位,但是你可以指定一個你自己的屬性名,示例如下:
import groovy.transform.Synchronized import java.util.concurrent.Executors import java.util.concurrent.TimeUnit class Counter { int cpt private final Object myLock = new Object() @Synchronized('myLock') int incrementAndGet() { cpt++ } int get() { cpt } }
@groovy.transform.WithReadLock和@groovy.transform.WithWriteLock
@WithReadLock AST轉換配合著@WithWriteLock轉換使用可以提供一個類似於JDK提供的ReentrantReadWriteLock鎖的讀寫同步功能。這個註解可以加在方法或靜態方法前面。它將會常見一個$reentrantLock final欄位(對於靜態方法是$REENTRANTLOCK)並且適當的同步程式碼將會自動新增,示例程式碼如下:
import groovy.transform.WithReadLock import groovy.transform.WithWriteLock class Counters { public final Map<String,Integer> map = [:].withDefault { 0 } @WithReadLock int get(String id) { map.get(id) } @WithWriteLock void add(String id, int num) { Thread.sleep(200) // emulate long computation map.put(id, map.get(id)+num) } }
等同於下面的程式碼:
import groovy.transform.WithReadLock as WithReadLock import groovy.transform.WithWriteLock as WithWriteLock public class Counters { private final Map<String, Integer> map private final java.util.concurrent.locks.ReentrantReadWriteLock $reentrantlock public int get(java.lang.String id) { $reentrantlock.readLock().lock() try { map.get(id) } finally { $reentrantlock.readLock().unlock() } } public void add(java.lang.String id, int num) { $reentrantlock.writeLock().lock() try { java.lang.Thread.sleep(200) map.put(id, map.get(id) + num ) } finally { $reentrantlock.writeLock().unlock() } } }
@WithReadLock和@WithWriteLock都支援指定一個鎖物件。那種情況下,必須允許使用者自己定義一個引用域,示例程式碼如下:
import groovy.transform.WithReadLock import groovy.transform.WithWriteLock import java.util.concurrent.locks.ReentrantReadWriteLock class Counters { public final Map<String,Integer> map = [:].withDefault { 0 } private final ReentrantReadWriteLock customLock = new ReentrantReadWriteLock() @WithReadLock('customLock') int get(String id) { map.get(id) } @WithWriteLock('customLock') void add(String id, int num) { Thread.sleep(200) // emulate long computation map.put(id, map.get(id)+num) } }
詳細的資訊:
2.1.5 更簡單的cloning和externalizing
Groovy 提供了兩種註解來支援Cloneable和Exteranlizable介面的實現。分別叫做@AutoClone和@AutoExternalize
@groovy.transform.AutoClone
@AutoClone註解使用各種策略實現了@java.lang.Cloneable介面,由於有style引數:
- 預設的AutoCloneStyle.CLONE策略先呼叫super.clone()方法然後呼叫每個可克隆屬性的clone()方法
- AutoCloneStyle.SIMPLE策略使用普通建構函式來複制所有屬性
- AutoCloneStyle.COPY_CONSTRUCTOR策略生成和使用一個copy建構函式
- AutoCloneStyle.SERIALIZATION策略使用序列化來克隆一個物件
import groovy.transform.AutoClone @AutoClone class Book { String isbn String title List<String> authors Date publicationDate }
等同於:
class Book implements Cloneable { String isbn String title List<String> authors Date publicationDate public Book clone() throws CloneNotSupportedException { Book result = super.clone() result.authors = authors instanceof Cloneable ? (List) authors.clone() : authors result.publicationDate = publicationDate.clone() result } }
注意,字串屬性並沒有處理,因為字串是不可變的,Object的clone方法只會複製字串的引用。對於原始資料型別和java.lang.Number的大多數子類也一樣。
除了克隆風格,@AutoStyle還支援多種引數:
引數 | 預設值 | 描述 | 示例 |
---|---|---|---|
excludes | 空列表 |
import groovy.transform.AutoClone import groovy.transform.AutoCloneStyle @AutoClone(style=AutoCloneStyle.SIMPLE,excludes='authors') class Book { String isbn String title List authors Date publicationDate } |
|
includeFields | false | 預設只會clone屬性值,將這個引數設定為true,將會使欄位也clone |
import groovy.transform.AutoClone import groovy.transform.AutoCloneStyle @AutoClone(style=AutoCloneStyle.SIMPLE,includeFields=true) class Book { String isbn String title List authors protected Date publicationDate } |
@groovy.transform.AutoExternalize
@AutoExternalize AST轉換是java.io.Externalizable類的輔助類。它會自動新增介面到類並且生成writeExternal和readExternal方法。示例程式碼如下:
import groovy.transform.AutoExternalize @AutoExternalize class Book { String isbn String title float price }
它將會轉化成:
class Book implements java.io.Externalizable { String isbn String title float price void writeExternal(ObjectOutput out) throws IOException { out.writeObject(isbn) out.writeObject(title) out.writeFloat( price ) } public void readExternal(ObjectInput oin) { isbn = (String) oin.readObject() title = (String) oin.readObject() price = oin.readFloat() } }
@AutoExternalize註解支援兩個引數讓你來自定義其行為:
引數 | 預設值 | 描述 | 示例 |
---|---|---|---|
excludes | 空列表 |
import groovy.transform.AutoExternalize @AutoExternalize(excludes='price') class Book { String isbn String title float price } |
|
includeFields | false | 預設情況下,只有屬性值會複製,如果設定為true,則欄位名也複製 |
import groovy.transform.AutoExternalize @AutoExternalize(includeFields=true) class Book { String isbn Str |