1. 程式人生 > >JavaSE第七十一講:Target及ElementType詳解

JavaSE第七十一講:Target及ElementType詳解

1. 繼續上一講內容,複習上一講內容我們講到了 Retention以及RetentionPolicy。這兩個都是成對出現的,因為Retention裡面包含了一個屬性value型別為 RetentionPolicy 列舉型別,它有三個列舉CLASS、RUNTIME、SOURCE。分別表示註解產生在三種不同的環境下。

2. 下面我們學習一下,如果在RUNTIME情況下,如何利用反射來讀取已經Class中的註解資訊。

定義Annotation時必須設定RetentionPolicy為RUNTIME,也就是可以在VM中讀取Annotation資訊。

1) 定義註解 MyAnnotation.java

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();
}
2) 定義一個類使用上面定義的註解
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!");
	}
}
3) 定義一個類利用反射來取得註解中的資訊
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();
}
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
	String hello() default "ahuier";
	String world();
}
編譯執行結果都沒輸出任何內容,因為設定為這兩個型別的列舉值之後,它們就不會在JVM執行期保留這些註解資訊,所以即便通過反射方式也是無法獲得他們的資訊的,也就是在執行 MyReflection.java這個類的 if(method.isAnnotationPresent(MyAnnotation.class))時候,它返回false,不執行裡面的內容了。

通過上面一個例子我們可以知道使用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

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();
}
2) 定義一個類使用這個註解,如下圖所示提示錯誤


提示錯誤原因是在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();
}

定義一個使用這個註解的類

package com.ahuier.annotation;

public class DocumentedTest {
	
	@DocumentedAnnotation(hello = "welcome")
	public void method(){
		System.out.println("hello world");
	}
}
編譯生成Java DOC文件:Eclipse --> Project --> Generate Javadoc --> 選擇需要生成doc的包

生成完成開啟生成API文件,檢視我們定義的method()方法如下圖所示,已經被標註了註解資訊了。


5. 子類是否繼承父類@Inherited,如下圖所示:


到此為止,我們的註解已經全部講完了,在實際開發中,我們一般很少自己去定義註解,但是通過這些內容我們可以明白註解的本源是什麼,可以在以後的實際開發中更好的學習好註解。