1. 程式人生 > >java 自定義註解

java 自定義註解

tar 基本用法 而且 最簡 ida outline plain 現在 傳播

參考鏈接:https://blog.csdn.net/hbzyaxiu520/article/details/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 自定義註解