標籤:getx 版本 假設 方法傳回值 element rar nis not ons
參考連結:6212969
JAVA自訂注釋(Target,Retention,Documented,Inherit)
定義自己的注釋類型
通過添加了一個小小的文法(Tiger 添加了大量的文法結構),Java 語言支援一種新的類型 —— 注釋類型(annotation type) 。注釋類型看起來很像普通的類,但是有一些特有的性質。最明顯的一點是,可以在類中以符號( @ )的形式注釋其他 Java 代碼。我將一步一步地介紹這個過程。
@interface 聲明
定義新的注釋類型與建立介面有很多類似之處,只不過 interface 關鍵字之前要有一個 @ 符號。清單 1 中給出的是一個最簡單的注釋類型的樣本:
清單 1. 非常簡單的注釋類型
package com.oreilly.tiger.ch06; /** * Marker annotation to indicate that a method or class * is still in progress. */ public @interface InProgress { } |
清單 1 的含義非常明顯。如果編譯這個注釋類型,並確信其位於類路徑中,那麼您就可以在自己的原始碼方法中使用它,以指出某個方法或類仍在處理中,如清單 2 所示:
清單 2. 使用定製的注釋類型
@com.oreilly.tiger.ch06.InProgress public void calculateInterest(float amount, float rate) { // Need to finish this method later } |
清單 1 所示注釋類型的使用方法和內建注釋類型的使用方法完全相同,只不過要同時使用名稱和所在的包來指示定製注釋。當然,一般的 Java 規則仍然適用,您可以匯入該注釋類型,直接使用 @InProgress 引用它。
|
不要漏掉本系列的另一部分
一定要閱讀本系列文章的“ 第 1 部分 ”,其中介紹了 Java 5.0 中的注釋。 |
|
新增成員
上面所示的基本用法還遠遠不夠健壯。您一定還記得“第 1 部分”中曾經提到的,注釋類型可以有成員變數(請參閱 參考資料 )。這一點非常有用,尤其是準備將注釋作為更加複雜的中繼資料,而不僅僅將它作為原始文檔使用的時候。程式碼分析工具喜歡加工大量的資訊,定製注釋可以提供這類資訊。
注釋類型中的資料成員被設定成使用有限的資訊進行工作。定義資料成員後不需要分別定義訪問和修改的方法。相反,只需要定義一個方法,以成員的名稱命名它。資料類型應該是該方法傳回值的類型。清單 3 是一個具體的樣本,它澄清了一些比較含糊的要求:
清單 3. 向注釋類型新增成員
package com.oreilly.tiger.ch06; /** * Annotation type to indicate a task still needs to be * completed. */ public @interface TODO { String value(); } |
儘管清單 3 看起來很奇怪,但這是注釋類型所要求的格式。清單 3 定義了一個名為 value 的字串,該注釋類型能夠接受它。然後,就可以像清單 4 中那樣使用注釋類型:
清單 4. 使用帶有成員值的注釋類型
@com.oreilly.tiger.ch06.InProgress @TODO("Figure out the amount of interest per month") public void calculateInterest(float amount, float rate) { // Need to finish this method later } |
這裡同樣沒有多少花樣。清單 4 假設已經引入了 com.oreilly.tiger.ch06.TODO ,因此原始碼中的注釋 不 需要包名作首碼。此外,需要注意的是,清單 4 中採用了簡寫的方法:將值 ("Figure out the amount of interest per month") 直接提供給注釋,沒有指定成員變數名。清單 4 和清單 5 是等價的,後者沒有採用簡寫形式:
清單 5. 清單 4 的“加長”版
@com.oreilly.tiger.ch06.InProgress @TODO(value="Figure out the amount of interest per month") public void calculateInterest(float amount, float rate) { // Need to finish this method later } |
當然作為編碼人員,我們都不願意跟這種“加長”版攪在一起。不過要注意,只有當注釋類型只有 一個 成員變數,而且變數名為 value 時,才能使用簡寫形式。如果不符合這個條件,那麼就無法利用這種特性。
設定預設值
迄今為止,您已經有了一個很好的起點,但是要做得完美,還有很長的一段路要走。您可能已經想到,下一步就要為注釋設定某個預設值。如果您希 望使用者指定某些值,但是只有這些值與預設值不同的時候才需要指定其他的值,那麼設定預設值就是一種很好的辦法。清單 6 用另一個定製注釋 —— 來自 清單 4 的 TODO 注釋類型的一個全功能版本,示範了這個概念及其實現:
清單 6. 帶有預設值的注釋類型
package com.oreilly.tiger.ch06; public @interface GroupTODO { public enum Severity { CRITICAL, IMPORTANT, TRIVIAL, DOCUMENTATION }; Severity severity() default Severity.IMPORTANT; String item(); String assignedTo(); String dateAssigned(); }
|
清單 6 中的 GroupTODO 注釋類型中添加了幾個新的變數。因為該注釋類型的成員變數不是一個,所以將一個變數命名為 value 沒有任何意義。只要成員變數多於一個,就應該儘可能準確地為其命名。因為不可能從 清單 5 所示的簡寫形式中獲益,所以您需要建立雖然稍微有點冗長,但是更容易理解的注釋類型。
清單 6 中出現的另一個新特性是注釋類型定義了自己的枚舉(枚舉,即 enumeration,通常也稱為 enums ,是 Java 5 的另一個新特性。它並沒有多麼地不同凡響,對注釋類型更是如此)。然後,清單 6 使用新定義的枚舉作為一個成員變數的類型。
最後,再回到我們的主題 —— 預設值。建立預設值的過程非常瑣碎,需要在成員聲明的後面添加關鍵字 default ,然後提供預設值。正如您所料,預設值的類型必須與成員變數聲明的類型完全相同。同樣,這也不是什麼火箭科學,只不過是詞法上的變異。清單 7 給出了一個具體應用中的 GroupTODO 注釋,其中 沒有 指定 severity 成員:
清單 7. 使用預設值
@com.oreilly.tiger.ch06.InProgress @GroupTODO( item="Figure out the amount of interest per month", assignedTo="Brett McLaughlin", dateAssigned="08/04/2004" ) public void calculateInterest(float amount, float rate) { // Need to finish this method later } |
清單 8 中使用了同一個注釋,但這一次給出了 severity 的值:
清單 8. 改寫預設值
@com.oreilly.tiger.ch06.InProgress @GroupTODO( severity=GroupTODO.Severity.DOCUMENTATION, item="Need to explain how this rather unusual method works", assignedTo="Jon Stevens", dateAssigned="07/30/2004" ) public void reallyConfusingMethod(int codePoint) { // Really weird code implementation } |
對注釋的注釋
結束關於注釋的討論之前(至少在本系列文章中),我想簡要地討論一下注釋的注釋。第 1 部分中所接觸的預定義注釋類型都有預定義的目的。但是在編寫自己的注釋類型時,注釋類型的目的並不總是顯而易見的。除了基本的文檔外,可能還要針對某個特 定的成員類型或者一群組成員類型編寫類型。這就要求您為注釋類型提供某種中繼資料,以便編譯器保證按照預期的目的使用注釋。
當然,首先想到的就是 Java 語言選擇的中繼資料形式 —— 注釋。您可以使用 4 種預定義的注釋類型(稱為 元注釋 )對您的注釋進行注釋。我將對這 4 種類型分別進行介紹。
指定目標
最明顯的元注釋就是允許何種程式元素具有定義的注釋類型。毫不奇怪,這種元注釋被稱為 Target 。但是在瞭解如何使用 Target 之前,您還需要認識另一個類,該類被稱為 ElementType ,它實際上是一個枚舉。這個枚舉定義了注釋類型可應用的不同程式元素。清單 9 給出了完整的 ElementType 枚舉:
清單 9. ElementType 枚舉
package java.lang.annotation; public enum ElementType { TYPE,// Class, interface, or enum (but not annotation) FIELD,// Field (including enumerated values) METHOD,// Method (does not include constructors) PARAMETER,// Method parameter CONSTRUCTOR,// Constructor LOCAL_VARIABLE,// Local variable or catch clause ANNOTATION_TYPE,// Annotation Types (meta-annotations) PACKAGE// Java package } |
清單 9 中的枚舉值意義很明確,您自己可以分析其應用的目標(通過後面的註解)。使用 Target 元注釋時,至少要提供這些枚舉值中的一個並指出注釋的注釋可以應用的程式元素。清單 10 說明了 Target 的用法:
清單 10. 使用 Target 元注釋
package com.oreilly.tiger.ch06; import java.lang.annotation.ElementType; import java.lang.annotation.Target; /** * Annotation type to indicate a task still needs to be completed */ @Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE}) public @interface TODO { String value(); } |
現在,Java 編譯器將把 TODO 應用於類型、方法、建構函式和其他注釋類型。這樣有助於避免他人誤用您的注釋類型(或者最好的地方是, 您自己 也不會因為疲憊而誤用它)。
設定保持性
下一個要用到的元注釋是 Retention 。這個元注釋和 Java 編譯器處理注釋的注釋類型的方式有關。編譯器有幾種不同選擇:
- 將注釋保留在編譯後的類檔案中,並在第一次載入類時讀取它。
- 將注釋保留在編譯後的類檔案中,但是在運行時忽略它。
- 按照規定使用注釋,但是並不將它保留到編譯後的類檔案中。
這三種選項用 java.lang.annotation.RetentionPolicy 枚舉表示,如清單 11 所示:
清單 11. RetentionPolicy 枚舉
package java.lang.annotation; public enum RetentionPolicy { SOURCE,// Annotation is discarded by the compiler CLASS,// Annotation is stored in the class file, but ignored by the VM RUNTIME// Annotation is stored in the class file and read by the VM } |
現在可以看出, Retention 元注釋類型使用清單 11 所示的枚舉值中的一個作為惟一的參數。可以將該元注釋用於您的注釋,如清單 12 所示:
清單 12. 使用 Retention 元注釋
@Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { // annotation type body } |
如清單 12 所示,這裡可以使用簡寫形式,因為 Retention 只有一個成員變數。如果要將保持性設為 RetentionPolicy.CLASS ,那麼什麼也不需要做,因為這就是預設行為。
添加公用文檔
下一個元注釋是 Documented 。這個元注釋也非常容易理解,部分原因是 Documented 是一個標記注釋。您應該還記得第 1 部分中曾經提到,標記注釋沒有成員變數。 Documented 表示注釋應該出現在類的 Javadoc 中。在預設情況下,注釋 不 包括在 Javadoc 中,如果花費大量時間注釋一個類、詳細說明未完成的工作、正確完成了什麼或者描述行為,那麼您應該記住這一點。
清單 13 說明了 Documented 元注釋的用法:
清單 13. 使用 Documented 元注釋
package com.oreilly.tiger.ch06; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Marker annotation to indicate that a method or class * is still in progress. */ @Documented @Retention(RetentionPolicy.RUNTIME) public @interface InProgress { }
|
Documented 的一個實用技巧是保持性策略。注意,清單 13 中規定注釋的保持性(retention)是 RUNTIME ,這是使用 Documented 注釋類型所 必需 的。Javadoc 使用虛擬機器從其類檔案(而非源檔案)中載入資訊。確保 VM 從這些類檔案中獲得產生 Javadoc 所需資訊的惟一方法是將保持性規定為 RetentionPolicy.RUNTIME 。這樣,注釋就會保留在編譯後的類檔案中 並且 由虛擬機器載入,然後 Javadoc 可以從中抽取出來添加到類的 HTML 文檔中。
設定繼承
最後一個元注釋 Inherited ,可能是最複雜、使用最少、也最容易造成混淆的一個。這就是說,我們簡單地看一看就可以了。
首先考慮這樣一種情況:假設您通過定製的 InProgress 注釋標記一個類正在開發之中,這完全沒有問題,對吧?這些資訊還會出現在 Javadoc 中,只要您正確地應用了 Documented 元注釋。現在,假設您要編寫一個新類,擴充那個還在開發之中的類,也不難,是不是?但是要記住,那個超類還在開發之中。如果您使用子類,或者查看它的文檔,根本沒有線索表明還有什麼地方沒有完成。您本來希望看到 InProgress 注釋被帶到子類中 —— 因為這是 繼承 的 —— 但情況並非如此。您必須使用 Inherited 元注釋說明所期望的行為,如清單 14 所示:
清單 14. 使用 Inherited 元注釋
package com.oreilly.tiger.ch06; import java.lang.annotation.Documented; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Marker annotation to indicate that a method or class * is still in progress. */ @Documented @Inherited @Retention(RetentionPolicy.RUNTIME) public @interface InProgress { }
|
添加 @Inherited 後,您將看到 InProgress 出現在注釋類的子類中。當然,您並不希望所有的注釋類型都具有這種行為(因此預設值是 不 繼承的)。比如, TODO 注釋就不會(也不應該)被傳播。但是對於這裡示範的情況, Inherited可能非常有用。
結束語
現在,您也許已經準備回到 Java 世界為所有的事物編寫文檔和注釋了。這不禁令我回想起人們瞭解 Javadoc 之後發生的事情。我們都陷入了文檔過濫的泥潭,直到有人認識到最好使用 Javadoc 來理清容易混淆的類或者方法。無論用 Javadoc 做了多少文章,也沒有人會去看那些易於理解的 getXXX() 和 setXXX() 方法。
注釋也可能出現同樣的趨勢,雖然不一定到那種程度。經常甚至頻繁地使用標準注釋類型是一種較好的做法。所有的 Java 5 編譯器都支援它們,它們的行為也很容易理解。但是,如果要使用定製注釋和元注釋,那麼就很難保證花費很大力氣建立的那些類型在您的開發環境之外還有什麼意 義。因此要謹慎。在合理的情況下使用注釋,不要荒謬使用。無論如何,注釋都是一種很好的工具,可以在開發過程中提供真正的協助。
java 自訂註解