JavaSE第七十一講:Target及ElementType詳解
1. 繼續上一講內容,複習上一講內容我們講到了 Retention以及RetentionPolicy。這兩個都是成對出現的,因為Retention裡面包含了一個屬性value型別為 RetentionPolicy 列舉型別,它有三個列舉CLASS、RUNTIME、SOURCE。分別表示註解產生在三種不同的環境下。
2. 下面我們學習一下,如果在RUNTIME情況下,如何利用反射來讀取已經Class中的註解資訊。
定義Annotation時必須設定RetentionPolicy為RUNTIME,也就是可以在VM中讀取Annotation資訊。
1) 定義註解 MyAnnotation.java
2) 定義一個類使用上面定義的註解package com.ahuier.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /* * 使用Retention修飾這個註解, * 告訴這個註解在使用的時候即可以用到.Class檔案,又能在執行過程中通過反射的方式讀取出來 * 這個註解用在Mytest類 和 它裡面的方法output()方法上 */ @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String hello() default "ahuier"; String world(); }
3) 定義一個類利用反射來取得註解中的資訊package com.ahuier.annotation; @MyAnnotation(hello = "beijing", world = "shanghai") public class MyTest { @MyAnnotation(hello = "tianjin", world = "shangdi") @Deprecated @SuppressWarnings("unchecked") //一個方法可以有多個註解所修飾 public void output(){ System.out.println("output something!"); } }
編譯執行結果:package com.ahuier.annotation; import java.lang.reflect.Method; /* * 通過反射獲取類MyTest中output()方法上所有的資訊,並且把資訊顯示出來 */ public class MyReflection { public static void main(String[] args) throws Exception{ MyTest myTest = new MyTest(); Class<MyTest> c = MyTest.class; /* * 通過反射獲取method物件,由於Method實現了AnnotatedElement介面,所以它可以呼叫獲取註解資訊的API來獲得註解資訊。 */ Method method = c.getMethod("output", new Class[]{}); /* * 檢視JDk Doc 文件,在Method類的父類AccessibleObject類中isAnnotationPresent() * 判斷某一個方法上面是否存在某一個註解 */ if(method.isAnnotationPresent(MyAnnotation.class)){ method.invoke(myTest, new Object[]{}); //如果存在這個註解,則去呼叫MyTest類中output()方法 } } }
output something!
【說明】:以上輸出方法內容,是因為我們在定義註解的時候,將註解資訊 Retention 設定為“RUNTIME”
為了更好的理解註解資訊三個列舉的不同,我們將註解 MyAnnotation.java 中的註解 Retention 設定為"CLASS" 和 "SOURCE" 如下程式碼所示:
@Retention(RetentionPolicy.CLASS) public @interface MyAnnotation { String hello() default "ahuier"; String world(); }
編譯執行結果都沒輸出任何內容,因為設定為這兩個型別的列舉值之後,它們就不會在JVM執行期保留這些註解資訊,所以即便通過反射方式也是無法獲得他們的資訊的,也就是在執行 MyReflection.java這個類的 if(method.isAnnotationPresent(MyAnnotation.class))時候,它返回false,不執行裡面的內容了。@Retention(RetentionPolicy.SOURCE) public @interface MyAnnotation { String hello() default "ahuier"; String world(); }
通過上面一個例子我們可以知道使用RUNTIME之後,是可以通過反射的方式呼叫到被註解修飾的方法,現在我們可以通過它們的一些API來獲得註解的一些某一個方法上面特定的註解資訊。
對上面第三個程式碼 MyReflection.java 繼續寫下去如下:
編譯執行結果:package com.ahuier.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.Method; /* * 通過反射獲取類MyTest中output()方法上所有的資訊,並且把資訊顯示出來 */ public class MyReflection { public static void main(String[] args) throws Exception{ MyTest myTest = new MyTest(); Class<MyTest> c = MyTest.class; /* * 通過反射獲取method物件,由於Method實現了AnnotatedElement介面,所以它可以呼叫獲取註解資訊的API來獲得註解資訊。 */ Method method = c.getMethod("output", new Class[]{}); /* * 檢視JDk Doc 文件,在Method類的父類AccessibleObject類中isAnnotationPresent() * 判斷某一個方法上面是否存在某一個註解 */ if(method.isAnnotationPresent(MyAnnotation.class)){ method.invoke(myTest, new Object[]{}); //如果存在這個註解,則去呼叫MyTest類中output()方法 /* * 檢視JDK Doc中Method類的public <T extends Annotation> T getAnnotation(Class<T> annotationClass)方法。 * 如果註解存在,則放回這個註解,否則返回為空 */ MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class); String hello = myAnnotation.hello(); //獲取 MyAnnotation中的 hello()註解資訊 String world = myAnnotation.world(); //獲取 MyAnnotation中的 world()註解資訊 System.out.println(hello + ", " + world); } /* * 檢視JDK Doc文件中 public Annotation[] getAnnotations() * 放回所有在特定元素上的註解,如果這個元素上沒有註解,則返回一個數組長度為0的陣列,而非返回空的陣列,所以不需要判斷陣列是否為空,直接遍歷陣列 */ Annotation[] annotations = method.getAnnotations(); for(Annotation annotation : annotations){ /* * 檢視JDK文件Annotation介面的 Class<? extends Annotation> annotationType() * 返回它所對應的Class物件 * 以下輸出: * com.ahuier.annotation.MyAnnotation * java.lang.Deprecated * 原因是MyTest中的output()上面的定義的三個註解中有兩個註解RetentionPolicy為“RUNTIME”,如果是“CLASS” 和 "SOURCE",則得不到的 */ System.out.println(annotation.annotationType().getName()); } } }
output something!
tianjin, shangdi
com.ahuier.annotation.MyAnnotation
java.lang.Deprecated
3. 下面我們學習Target,限定annotation使用物件@Target,如下圖所示:
這邊的@Target與Retention有異曲同工之妙,Retention搭配RetentionPolicy來指定它的列舉值是什麼。而Target也搭配ElementType表示這注解可以修飾哪些目標。
檢視JDK Doc文件Target,它有一個value,是ElementType 列舉型別,檢視ElementTyped,它的列舉值如下所示:
ANNOTATION_TYPE: 註解只能修飾註解,不能修飾其他的東西
CONSTRUCTOR: 註解只能修飾構造方法
FIELD: 註解只能修飾屬性(成員變數)
LOCAL_VARIABLE: 註解只能修飾區域性變數
METHOD: 註解只能修飾方法
PACKAGE: 註解只能修飾包
PARAMETER: 註解只能修飾方法的引數
TYPE: 註解只能修飾類、介面、列舉
如下圖所示:
下面我們來通過例子學習上面的內容:
1) 定義一個註解MyTarget.java
2) 定義一個類使用這個註解,如下圖所示提示錯誤package com.ahuier.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Target; /* * @Target(ElementType.METHOD)表示這個註解只能用來修飾方法 */ @Target(ElementType.METHOD) public @interface MyTarget { String value(); }
提示錯誤原因是在MyTarget這個註解中我們定義的註解只能用來修飾方法,所以在修飾類的時候會提示錯誤。修改的方法是將MyTarget.java註解Target的value改為TYPE則可以,如下程式碼所示:
@Target(ElementType.TYPE) public @interface MyTarget { String value(); }
4. 要求為API檔案@Documented
定義一個帶@Documented的註解
package com.ahuier.annotation; import java.lang.annotation.Documented; /* * @Documented 表示帶@Documented修飾的東西會新增到生成文件的API中去 */ @Documented public @interface DocumentedAnnotation { String hello(); }
定義一個使用這個註解的類
編譯生成Java DOC文件:Eclipse --> Project --> Generate Javadoc --> 選擇需要生成doc的包package com.ahuier.annotation; public class DocumentedTest { @DocumentedAnnotation(hello = "welcome") public void method(){ System.out.println("hello world"); } }
生成完成開啟生成API文件,檢視我們定義的method()方法如下圖所示,已經被標註了註解資訊了。
5. 子類是否繼承父類@Inherited,如下圖所示:
到此為止,我們的註解已經全部講完了,在實際開發中,我們一般很少自己去定義註解,但是通過這些內容我們可以明白註解的本源是什麼,可以在以後的實際開發中更好的學習好註解。