Java Annotation详解
http://djjchobits.iteye.com/blog/569000
元數據的作用
如果要對于元數據的作用進行分類,目前還沒有明確的定義,不過我們可以根據它所起的作用,大致可分為三類:
l ???????? 編寫文檔:通過代碼里標識的元數據生成文檔。
l ???????? 代碼分析:通過代碼里標識的元數據對代碼進行分析。
l ???????? 編譯檢查:通過代碼里標識的元數據讓編譯器能實現基本的編譯檢查。
?
基本內置注釋
??? @Override 注釋能實現編譯時檢查,你可以為你的方法添加該注釋,以聲明該方法是用于覆蓋父類中的方法。如果該方法不是覆蓋父類的方法,將會在編譯時報錯。例如我們為某類重寫toString() 方法卻寫成了tostring() ,并且我們為該方法添加了@Override 注釋;
???? @Deprecated 的作用是對不應該在使用的方法添加注釋,當編程人員使用這些方法時,將會在編譯時顯示提示信息,它與javadoc 里的 @deprecated 標記有相同的功能,準確的說,它還不如javadoc @deprecated ,因為它不支持參數,
注意:要了解詳細信息,請使用 -Xlint:deprecation 重新編譯。
??? @SuppressWarnings 與前兩個注釋有所不同,你需要添加一個參數才能正確使用,這些參數值都是已經定義好了的,我們選擇性的使用就好了,參數如下:
?
deprecation?? 使用了過時的類或方法時的警告
unchecked? 執行了未檢查的轉換時的警告,例如當使用集合時沒有用泛型 (Generics) 來指定集合保存的類型
fallthrough?? 當 Switch 程序塊直接通往下一種情況而沒有 Break 時的警告
path?? 在類路徑、源文件路徑等中有不存在的路徑時的警告
serial 當在可序列化的類上缺少 serialVersionUID 定義時的警告
finally??? 任何 finally 子句不能正常完成時的警告
all 關于以上所有情況的警告
?
注意:要了解詳細信息,請使用 -Xlint:unchecked 重新編譯。
?
定制注釋類型
??? 好的,讓我們創建一個自己的注釋類型(annotation type )吧。它類似于新創建一個接口類文件,但為了區分,我們需要將它聲明為@interface, 如下例:
public @interface NewAnnotation {
?
}
?
使用定制的注釋類型
??? 我們已經成功地創建好一個注釋類型NewAnnotation ,現在讓我們來嘗試使用它吧,如果你還記得本文的第一部分,那你應該知道他是一個標記注釋,使用也很容易,如下例:
public class AnnotationTest {
?
??? @NewAnnotation
??? public static void main(String[] args) {
???
??? }
}
?
添加變量
??? J2SE 5.0 里,我們了解到內置注釋@SuppressWarnings() 是可以使用參數的,那么自定義注釋能不能定義參數個數和類型呢?答案是當然可以,但參數類型只允許為基本類型、String 、Class 、枚舉類型等,并且參數不能為空。我們來擴展NewAnnotation ,為之添加一個String 類型的參數,示例代碼如下:
public @interface NewAnnotation {
?
??? String value();
}
??? 使用該注釋的代碼如下:正如你所看到的,該注釋的使用有兩種寫法,這也是在之前的文章里所提到過的。如果你忘了這是怎么回事,那就再去翻翻吧。
public class AnnotationTest {
?
??? @NewAnnotation("Just a Test.")
??? public static void main(String[] args) {
??????? sayHello();
??? }
???
??? @NewAnnotation(value="Hello NUMEN.")
??? public static void sayHello() {
??????? // do something
??? }
}
?
為變量賦默認值
??? 我們對Java 自定義注釋的了解正在不斷的增多,不過我們還需要更過,在該條目里我們將了解到如何為變量設置默認值,我們再對NewAnnotaion 進行修改,看看它會變成什么樣子,不僅參數多了幾個,連類名也變了。但還是很容易理解的,我們先定義一個枚舉類型,然后將參數設置為該枚舉類型,并賦予默認值。
public @interface Greeting {
?
??? public enum FontColor {RED, GREEN, BLUE};
?
??? String name();
?
??? String content();
???
??? FontColor fontColor() default FontColor.BLUE;
}
?
限定注釋使用范圍
??? 當我們的自定義注釋不斷的增多也比較復雜時,就會導致有些開發人員使用錯誤,主要表現在不該使用該注釋的地方使用。為此,Java 提供了一個ElementType 枚舉類型來控制每個注釋的使用范圍,比如說某些注釋只能用于普通方法,而不能用于構造函數等。下面是Java 定義的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
}
??? 下面我們來修改Greeting 注釋,為之添加限定范圍的語句,這里我們稱它為目標(Target )使用方法也很簡單,如下:
?
@Target( { ElementType.METHOD, ElementType.CONSTRUCTOR })
public @interface Greeting {
}
正如上面代碼所展示的,我們只允許Greeting 注釋標注在普通方法和構造函數上,使用在包申明、類名等時,會提示錯誤信息。
?
注釋保持性策略
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
}
??? RetentionPolicy 的使用方法與ElementType 類似,簡單代碼示例如下:
@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.METHOD, ElementType.CONSTRUCTOR })
?
文檔化功能
??? Java 提供的Documented 元注釋跟Javadoc 的作用是差不多的,其實它存在的好處是開發人員可以定制Javadoc 不支持的文檔屬性,并在開發中應用。它的使用跟前兩個也是一樣的,簡單代碼示例如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.METHOD, ElementType.CONSTRUCTOR })
public @interface Greeting {
}
?
值得大家注意的是,如果你要使用@Documented 元注釋,你就得為該注釋設置RetentionPolicy.RUNTIME 保持性策略。為什么這樣做,應該比較容易理解,這里就不提了。
?
標注繼承
繼承應該是Java 提供的最復雜的一個元注釋了,它的作用是控制注釋是否會影響到子類,簡單代碼示例如下:
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.METHOD, ElementType.CONSTRUCTOR })
public @interface Greeting {
}
?
讀取注釋信息
??? 當我們想讀取某個注釋信息時,我們是在運行時通過反射來實現的,如果你對元注釋還有點印象,那你應該記得我們需要將保持性策略設置為RUNTIME ,也就是說只有注釋標記了@Retention(RetentionPolicy.RUNTIME) 的,我們才能通過反射來獲得相關信息,下面的例子我們將沿用前面幾篇文章中出現的代碼,并實現讀取AnnotationTest 類所有方法標記的注釋并打印到控制臺。好了,我們來看看是如何實現的吧:
public class AnnotationIntro {
?
??? public static void main(String[] args) throws Exception {
?
??????? Method[] methods = Class.forName(
??????????????? "com.gelc.annotation.demo.customize.AnnotationTest")
??????????????? .getDeclaredMethods();
??????? Annotation[] annotations;
?
??????? for (Method method : methods) {
??????????? annotations = method.getAnnotations();
??????????? for (Annotation annotation : annotations) {
??????????????? System.out.println(method.getName() + " : "
??????????????????????? + annotation.annotationType().getName());
??????????? }
?
********************************************************************************
?
Annotation(注解)
Annotation對于程序運行沒有影響,它的目的在于對編譯器或分析工具說明程序的某些信息,您可以
在包,類,方法,域成員等加上Annotation.每一個Annotation對應于一個實際的Annotation類型.
1??限定Override父類方法@Override
??java.lang.Override是J2SE5.0中標準的Annotation類型之一,它對編譯器說明某個方法必須
??是重寫父類中的方法.編譯器得知這項信息后,在編譯程序時如果發現被@Override標示的方法
??并非重寫父類中的方法,就會報告錯誤.
??例,如果在定義新類時想要重寫Object類的toString()方法,可能會寫成這樣:
??public class CustomClass{
????public String ToString(){
??????return "customObject";
????}
??}
??在編寫toString()方法時,因為輸入錯誤或其他的疏忽,將之寫成ToString()了,編譯這個類時
??并不會出現任何的錯誤,編譯器不會知道您是想重寫toString()方法,只會以為是定義了一個新
??的ToString()方法.
??可以使用java.lang.Override這個Annotation類型,在方法上加一個@Override的Annotation
??這可以告訴編譯器現在定義的這個方法,必須是重寫父類中的同包方法.
??public class CustomClass{
????@Override
????public String toString(){
??????return "coustomObject";
????}
??}
??java.lang.Override是一個Marker Annotation,簡單地說就是用于標示的Annotation,Annotation
??名稱本身表示了要給工具程序的信息。
??
??Annotation類型與Annotation實際上是有區分的,Annotation是Annotation類型的實例,例如
??@Override是個Annotation,它是java.lang.Override類型的一個實例,一個文件中可以有很多
??個@Override,但它們都是屬于java.lang.Override類型。
??
2??標示方法為Deprecated @Deprecated
??java.lang.Deprecated也是J2SE5.0中標準的Annotation類型之一。它對編譯器說明某個方法已經不
??建議使用。如果有開發人員試圖使用或重寫被@Deprecated標示的方法,編譯器必須提出警告信息。
??例:
??public class Something{
????@Deprecated
????public Something getSomething(){
??????return new Something();
????}
??}
??如果有人試圖在繼承這個類后重寫getSomething()方法,或是在程序中調用getSomething()方法,
??則編譯時會有警告出現。
??java.lang.Deprecated也是一個Marker Annotation簡單地說就是用于標示。
??
3??抑制編譯器警告 @SuppressWarnings
??java.lang.SuppressWarnings也是J2SE5.0中標準的Annotation類型之一,它對編譯器說明某個方法
??中若有警告信息,則加以抑制,不用在編譯完成后出現警告。
??例:
??public class SomeClass2{
????@SuppressWarnings(value={"unchecked"});
????public void doSomething(){
??????Map map = new HashMap();
??????map.put("some","thing");
????}
??}
??這樣,編譯器將忽略unchecked的警告,您也可以指定忽略多個警告:
??@SuppressWarnings(value={"unchecked","deprecation"});
??@SuppressWarnings是所謂的Single-Value Annotation,因為這樣的Annotation只有一個成員,稱為
??value成員,可在使用Annotation時作額外的信息指定。
??
??
??
自定義Annotation類型
??可以自定義Annotation類型,并使用這些自定義的Annotation類型在程序代碼中使用Annotation,這些
??Annotation將提供信息給程序代碼分析工具。
??首先來看看如何定義Marker Annotation,也就是Annotation名稱本身即提供信息。對于程序分析工具來
??說,主要是檢查是否有Marker Annotation的出現,并做出對應的動作。要定義一個Annotation所需的動作
??,就類似于定義一個接口,只不過使用的是@interface。
??例:
??public @interface Debug{}
??
??由于是一個Marker Annotation,所以沒有任何成員在Annotation定義中。編譯完成后,就可以在程序代碼
??中使用這個Annotation。
??public class SomeObject{
????@Debug
????public void doSomething(){
?????......
????}
??}
??稍后可以看到如何在Java程序中取得Annotation信息(因為要使用Java程序取得信息,所以還要設置
??meta-annotation,稍后會談到)
??
??接著來看看如何定義一個Single-Value Annotation,它只有一個Value成員。
??例:
??public @interface UnitTest{
????String value();
??}
??實際上定義了value()方法,編譯器在編譯時會自動產生一個value的域成員,接著在使用UnitTest
??Annotation時要指定值。如:
??public class MathTool{
???@UnitTest("GCD")
???public static int gcdOf(int num1,int num2){
?????...............
???}
??}
??@UnitTest("GCD")實際上是@UnitTest(value="GCD")的簡便寫法,value也可以是數組值。如:
??public @interface FunctionTest{
????String[] value();
??}
??在使用時,可以寫成@FunctionTest({"method1","method2"})這樣的簡便形式。或是
??@FunctionTest(value={"method1","method2"})這樣的詳細形式.
??
??也可以對value成員設置默認值,使用default關鍵詞即可。
??例:
??public @interface UnitTest2{
????String value() default "noMethod";
??}
??這樣如果使用@UnitTest2時沒有指定value值,則value默認就是noMethod.
??
??
??也可以為Annotation定義額外的成員,以提供額外的信息給分析工具,如:
??public @interface Process{
????public enum Current{NONE,REQUIRE,ANALYSIS,DESIGN,SYSTEM};
????Current current() default Current.NONE;
????String tester();
????boolean ok();
??}
??運用:
??public class Application{
????@process(
??????current = Process.Current.ANALYSIS,
??????tester = "Justin Lin",
??????ok = true
????)
????public void doSomething(){
??????...........
????}
??}
??當使用@interface自行定義Annotation類型時,實際上是自動繼承了
??java.lang.annotation接口,并由編譯器自動完成其他產生的細節,并且在定義Annotation類型時,
??不能繼承其他的Annotation類型或接口.
??定義Annotation類型時也可以使用包機制來管理類。由于范例所設置的包都是onlyfun.caterpillar,
??所以可以直接使用Annotation類型名稱而不指定包名,但如果是在別的包下使用這些自定義的Annotation
??,記得使用import告訴編譯器類型的包們置。
??如:
??import onlyfun.caterpillar.Debug;
??public class Test{
????@Debug
????public void doTest(){
??????......
????}
??}
??或是使用完整的Annotation名稱.如:
??public class Test{
????@onlyfun.caterpillar.Debug
????public void doTest(){
??????......
????}
??}
??
??
??
meta-annotation
??所謂neta-annotation就是Annotation類型的數據,也就是Annotation類型的Annotation。在定義
??Annotation類型時,為Annotation類型加上Annotation并不奇怪,這可以為處理Annotation類型
??的分析工具提供更多的信息。
??
1??告知編譯器如何處理annotation @Retention
??java.lang.annotation.Retention類型可以在您定義Annotation類型時,指示編譯器該如何對待自定
??義的Annotation類型,編譯器默認會將Annotation信息留在.class文件中,但不被虛擬機讀取,而僅用
??于編譯器或工具程序運行時提供信息。
??
??在使用Retention類型時,需要提供java.lang.annotation.RetentionPolicy的枚舉類型。
??RetentionPolicy的定義如下所示:
??package java.lang.annotation;
??public enum RetentionPolicy{
????SOURCE,//編譯器處理完Annotation信息后就沒有事了
????CLASS,//編譯器將Annotation存儲于class文件中,默認
????RUNTIME //編譯器將Annotation存儲于class文件中,可由VM讀入
??}
??
??RetentionPolicy為SOURCE的例子是@SuppressWarnings,這個信息的作用僅在編譯時期告知
??編譯器來抑制警告,所以不必將這個信息存儲在.class文件中。
??
??RetentionPolicy為RUNTIME的時機,可以像是您使用Java設計一個程序代碼分析工具,您必須讓VM能讀出
??Annotation信息,以便在分析程序時使用,搭配反射機制,就可以達到這個目的。\
??
??J2SE6.0的java.lang.reflect.AnnotatedElement接口中定義有4個方法:
??public Annotation getAnnotation(Class annotationType)
??public Annotation[] getAnnotations();
??public Annotation[] getDeclaredAnnotations()
??public boolean isAnnotationPresent(Class annotationType);
??
??Class,Constructor,field,Method,Package等類,都實現了AnnotatedElement接口,所以可以從這些
??類的實例上,分別取得標示于其上的Annotation與相關信息。由于在執行時讀取Annotation信息,所以定
??義Annotation時必須設置RetentionPolicy為RUNTIME,也就是可以在VM中讀取Annotation信息。
??例:
??package onlyfun.caterpillar;
??import java.lang.annotation.Retention;
??import java.lang.annotation.RetentionPllicy;
??
??@Retention(RetentionPolicy.RUNTIME)
??public @interface SomeAnnotation{
????String value();
????String name();
??}
??由于RetentionPolicy為RUNTIME,編譯器在處理SomeAnnotation時,會將Annotation及給定的相關信息
??編譯至.class文件中,并設置為VM可以讀出Annotation信息。接下來:
??package onlyfun.caterpillar;
??public class SomeClass3{
????@SomeAnotation{
??????value="annotation value1",
??????name="annotation name1"
????}
????public void doSomething(){
??????......
????}
??}
??
??
??現在假設要設計一個源代碼分析工具來分析所設計的類,一些分析時所需的信息已經使用Annotation標示于類
??中了,可以在執行時讀取這些Annotation的相關信息。例:
??
??package onlyfun.caterpillar;
??
??import java.lang.annotation.Annotation;
??import java.lang.reflect.Method;
??
??public class AnalysisApp{
????public static void main(String [] args) throws NoSuchMethodException{
??????Class<SomeClass3> c = SomeClass3.class;
??????//因為SomeAnnotation標示于doSomething()方法上
??????//所以要取得doSomething()方法的Method實例
??????Method method = c.getMethod("doSomething");
??????//如果SomeAnnotation存在
??????if(method.isAnnotationPresent(SomeAnnotation.class){
????????System.out.println("找到@SomeAnnotation");
????????//取得SomeAnnotation
????????SomeAnnotation annotation = method.getAnnotation(SomeAnnotation.class);
????????//取得vlaue成員值
????????System.out.println(annotation.value);
????????//取得name成員值
????????System.out.println(annotation.name());
??????}else{
????????System.out.println("找不到@SomeAnnotation");
??????}
??????//取得doSomething()方法上所有的Annotation
??????Annotation[] annotations = method.getAnnotations();
??????//顯示Annotation名稱
??????for(Annotation annotation : annotations){
????????System.out.println("Annotation名稱:"+annotation.annotationType().getName());
??????}
????}
??}
??若Annotation標示于方法上,就要取得方法的Method代表實例,同樣的,如果Annotation標示于類或包上,
??就要分別取得類的Class代表的實例或是包的Package代表的實例。之后可以使用實例上的getAnnotation()
??等相關方法,以測試是否可取得Annotation或進行其他操作。
??
2??限定annotation使用對象 @Target
??在定義Annotation類型時,使用java.lang.annotation.Target可以定義其適用的時機,在定義時要指定
??java.lang.annotation.ElementType的枚舉值之一。
??public enum elementType{
????TYPE,//適用class,interface,enum
????FIELD,//適用于field
????METHOD,//適用于method
????PARAMETER,//適用method上之parameter
????CONSTRUCTOR,//適用constructor
????LOCAL_VARIABLE,//適用于區域變量
????ANNOTATION_TYPE,//適用于annotation類型
????PACKAGE,//適用于package
??}
??
??舉例,假設定義Annotation類型時,要限定它只能適用于構造函數與方法成員,則:
??package onlyfun.caterpillar;
??
??import java.lang.annotation.Target;
??import java.lang.annotation.ElementType;
??
??@Target({ElementType.CONSTRUCTOR,ElementType.METHOD})
??public @interface MethodAnnotation{}
??
??將MethodAnnotation標示于方法之上,如:
??
??public class SomeoneClass{
????@onlyfun.caterpillar.MethodAnnotation
????public void doSomething(){
??????......
????}
??}
??
3??要求為API文件的一部分 @Documented
?
??在制作Java Doc文件時,并不會默認將Annotation的數據加入到文件中.Annnotation用于標示程序代碼以便
??分析工具使用相關信息,有時Annotation包括了重要的信息,您也許會想要在用戶制作Java Doc文件的同時,
??也一并將Annotation的信息加入到API文件中。所以在定義Annotation類型時,可以使用
??java.lang.annotation.Documented.例:
??
??package onlyfun.caterpillar;
??
??import java.lang.annotation.Documented;
??import java.lang.annotation.Retention;
??import java.lang.annotation.RetentionPolicy;
??
??@Documented
??@Retention(RetentionPolicy.RUNTIME)
??public @interface TwoAnnotation{}
??
??使用java.lang.annotation.Documented為定義的Annotation類型加上Annotation時,必須同時使用Retention
??來指定編譯器將信息加入.class文件,并可以由VM讀取,也就是要設置RetentionPolicy為RUNTIME。接著可以使
??用這個Annotation,并產生Java Doc文件,這樣可以看到文件中包括了@TwoAnnotation的信息.
??
4??子類是否可以繼承父類的annotation @Inherited
??在定義Annotation類型并使用于程序代碼上后,默認父類中的Annotation并不會被繼承到子類中。可以在定義
??Annotation類型時加上java.lang.annotation.Inherited類型的Annotation,這讓您定義的Annotation類型在
??被繼承后仍可以保留至子類中。
??例:
??package onlyfun.caterpillar;
??
??import java.lang.annotation.Retention;
??import java.lang.annotation.RetentionPolicy;
??import java.lang.annotation.Inherited;
??
??@Retention(RetentionPolicy.RUNTIME)
??@Inherited
??public @interface ThreeAnnotation{
????String value();
????String name();
??}
??可以在下面的程序中使用@ThreeAnnotation:
??public class SomeoneClass{
????@onlyfun.caterpillar.ThreeAnnotation(
??????value = "unit",
??????name = "debug1"
????)
????public void doSomething(){
??????.....
????}
??}
??如果有一個類繼承了SomeoneClass類,則@ThreeAnnotation也會被繼承下來。
?
總結
以上是生活随笔為你收集整理的Java Annotation详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 使用 touch 修改文件的
- 下一篇: Java Process.exitVal