前言
本文是我整理的java反射的一些知識,其中大部分內容是翻譯http://tutorials.jenkov.com/java-reflection/index.html的。
JAVA反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法;這種動態獲取的信息以及動態調用對象的方法的功能稱為Java語言的反射機制。
Java反射機制是Java語言被視為“準動態”語言的關鍵性質。Java反射機制的核心就是允許在運行時通過Java Reflection APIs來取得已知名字的class類的內部信息(包括其modifiers(諸如public, static等等)、superclass(例如Object)、實現interfaces(例如Serializable),也包括fields和methods的所有信息),動態地生成此類,并調用其方法或修改其域(甚至是本身聲明為PRivate的域或方法)。
Java反射機制主要提供了以下功能:在運行時判斷任意一個對象所屬的類;在運行時構造任意一個類的對象;在運行時判斷任意一個類所具有的成員變量和方法;在運行時調用任意一個對象的方法;生成動態代理。
Class對象是Java反射的基礎,它包含了與類相關的信息,事實上,Class對象就是用來創建類的所有對象的。Class對象是java.lang.Class<T>這個類生成的對象,其中類型參數T表示由此 Class 對象建模的類的類型。例如,String.class的類型是 Class<String>;如果將被建模的類未知,則使用Class<?>。以下是Java API
的描述:
Class
類的實例表示正在運行的 Java應用程序中的類和接口。枚舉是一種類,注釋是一種接口。每個數組屬于被映射為Class對象的一個類,所有具有相同元素類型和維數的數組都共享該Class
對象。基本的Java類型(boolean
、byte
、char
、short
、int
、long
、float
和double
)和關鍵字void
也表示為Class
對象。
Class
沒有公共構造方法。Class
對象是在加載類時由Java虛擬機以及通過調用類加載器中的defineClass
方法自動構造的。
實際上,每個類都有一個Class對象。換言之,每當編寫并且編譯了一個新類,就會產生一個Class對象(更恰當的說,是被保存在一個同名的.class文件中)。如果我們想生成這個類的對象,運行這個程序的Java虛擬機(JVM)將使用類加載器子系統,類加載器首先檢查這個類的Class對象是否已經加載。如果尚未加載,默認的類加載器就會根據類名查找.class文件,并將其載入,一旦某個類的Class對象被載入內存,它就被用來創建這個類的所有對象。
獲取Class對象有三種方式:
(1) 通過實例變量的getClass()方法。例如:
Class c1 = new String("abc").getClass();
(2) 通過Class類的靜態方法——forName()來實現,例如:
Class class =Class.forName(className);
注意:當使用Class.forName()方法時,你必須提供完全限定類名。即類名要包括所有包
名。例如,如果MyObject是位于包com.jenkov.myapp下,那么類的完全限定名稱是com.jenkov.myapp.MyObject。如果在運行時類路徑上找不到類,Class.forName()方法會拋出一個ClassNotFoundException。
(3) 使用類字面常量或TYPE字段,例如:
Class myObjectClass= MyObject.class;(類字面常量不僅可以應用于普通的類,也可以應用
于接口、數組以及基本數據類型),這種方式不僅更簡單,而且更安全,因為它在編譯時就會受到檢查,并且根除了對forName方法的調用,所以也更高效,建議使用“.class”的形式。
Class c = Integer.TYPE;(TYPE是基本數據類型的包裝類型的一個標準字段,它是一
個引用,指向對應的基本數據類型的Class對象),附表如下,兩邊等價:
boolean.class | Boolean.TYPE |
char.class | Character.TYPE |
byte.class | Byte.TYPE |
short.class | Short.TYPE |
int.class | Integer.TYPE |
long.class | Long.TYPE |
float.class | Float.TYPE |
double.class | Double.TYPE |
void.class | Void.TYPE |
使用Java反射,你可以在運行時檢查Java類。檢查類是使用反射時經常做的第一件事情。從類中可以獲取以下信息:
(1) 類名
(2) 類修飾符 (public, private, synchronized等)
(3) 包信息
(4) 父類
(5) 實現的接口
(6) 構造函數
(7) 方法
(8) 字段
(9) 注解
從Class對象中可以獲取兩個不同的類名。完全限定類名(包括包名)可以使用getName()或getCanonicalName()方法獲取,例如:
Class aClass = MyObject.class;String className = aClass.getName();String className1 = aClass.getCanonicalName();如果想要獲取不含包名的類名可以使用
getSimpleName()
方法,如下:Class aClass = MyObject.class;String simpleClassName = aClass.getSimpleName();3.2修飾符
使用Class對象可以獲取一個類的修飾符.類的修飾符即關鍵字"public","private", "static"等. 如下:
Class aClass = MyObject.class;int modifiers = aClass.getModifiers();修飾符被包裝進一個int
內,
每一個修飾符都是一個標志位(置位或清零)。可以使用java.lang.reflect.Modifier
類
中的以下方法來檢驗修飾符:Modifier.isAbstract(int modifiers)Modifier.isFinal(int modifiers)Modifier.isInterface(int modifiers)Modifier.isNative(int modifiers)Modifier.isPrivate(int modifiers)Modifier.isProtected(int modifiers)Modifier.isPublic(int modifiers)Modifier.isStatic(int modifiers)Modifier.isStrict(int modifiers)Modifier.isSynchronized(int modifiers)Modifier.isTransient(int modifiers)Modifier.isVolatile(int modifiers)3.3包信息
使用Class對象可以獲取包信息,如下:
Class aClass = MyObject.class;Package package = aClass.getPackage();String packageName = package.getname();從Package對象中你可以訪問諸如名字等包信息。您還可以訪問類路徑上這個包位于JAR文件中Manifest這個文件中指定的信息。例如,你可以在Manifest文件中指定包的版本號。可以在java.lang.Package中了解更多包類信息。
3.4父類
通過
Class
對象可以獲取類的父類
,如下:Class aClass = MyObject.class;Class superclass = aClass.getSuperclass();父類的Class對象和其它Class對象一樣是一個Class對象,可以繼續使用反射.
3.5實現的接口
通過給定的類可以獲取這個類所實現的接口列表,如下:
Class aClass = MyObject.class;Class[] interfaces = aClass.getInterfaces();一個類可以實現多個接口。因此返回一個Class數組。在Java反射機制中,接口也由Class對象表示。
注意:只有給定類聲明實現的接口才會返回。例如,如果類A的父類B實現了一個接口C,但類A并沒有聲明它也實現了C,那么C不會被返回到數組中。即使類A實際上實現了接口C,因為它的父類B實現了C。
為了得到一個給定的類實現接口的完整列表,需要遞歸訪問類和其超類。
3.6構造函數
使用Class對象可以獲取類的構造函數,如下:
Class aClass = MyObject.class;Constructor[] constructors = aClass.getConstructors();關于構造函數更詳細信息參見 構造函數這節。
3.7方法
使用Class對象可以獲取類的方法,如下:
Class aClass = MyObject.class;Method[] methods = aClass.getMethods();關于方法更詳細信息參見方法這節.
3.8字段
使用Class對象可以獲取類的字段(成員變量),如下:
Class aClass = MyObject.class;Field[] fields = aClass.getFields();關于字段更詳細信息參見 字段這節.
3.9注解
使用Class對象可以獲取類的注解,如下:
Class aClass = MyObject.class;Annotation[] annotations = aClass.getAnnotations();關于注解更詳細信息參見注解這節.
4. 構造函數
使用Java反射可以在運行時檢查類的構造函數并實例化對象。這是通過Java類java.lang.reflect.Constructor來實現的。以下是Java Construcor對象的更多細節:
(1) 獲取Constructor對象
(2) 構造函數參數
(3) 使用Constructor對象實例化對象
4.1獲取Constructor對象
Constructor
類是從Class對象獲取的,舉例:Class aClass = MyObject.class;Constructor[] constructors = aClass.getConstructors();
Constructor
數組為每一個在類中聲明的
public
構造函數保存一個
Constructor
實例
。如果知道要訪問的構造函數確切的參數類型,可以不獲取構造函數數組。本示例將返回給定類中接受一個字符串作為參數的公共構造函數。
Class aClass = MyObject.class;//MyObject有一個參數為字符串的公共構造函數Constructor constructor = aClass.getConstructor(new Class[]{String.class});如果沒有匹配給定的構造函數參數,在這個例子當中是
String.class
,會拋出NoSuchMethodException
異常.4.2構造函數參數
可以知道給定的構造函數接受什么參數,如下:
Class aClass = MyObject.class;//MyObject有一個參數為字符串的公共構造函數Constructor constructor = aClass.getConstructor(new Class[]{String.class});Class[] parameterTypes = constructor.getParameterTypes();4.3使用Constructor對象實例化對象
可以像這樣實例化對象:
//獲取使用字符串作為參數的constructorConstructor constructor = MyObject.class.getConstructor(String.class);MyObject myObject = (MyObject)constructor.newInstance("constructor-arg1");
Constructor.newInstance()
方法使用可變長度的參數,但是在調用構造函數時必須為每一個參數提供一個準確的參量.在這個例子中構造函數接受一個字符串作為參數,所以必須要提供一個字符串。5. 字段
使用Java反射你可以在運行時檢查類的字段(成員變量)并 get / set它們.這是通過Java類
java.lang.reflect.Field
來完成的
.以下是JavaField
對象更多細節:(1) 獲取Field 對象
(2) 字段名稱
(3) 字段類型
(4) 獲取和設置字段值
5.1獲取Field 對象
Field
類是從Class
對象獲取的.舉例:Class aClass = MyObject.class;Field[] methods = aClass.getFields();
Field
數組為類中聲明的每一個
public
字段
保存一個
Field
實例。如果知道要訪問的字段名稱,可以這樣獲取它:
Class aClass = MyObject.classField field = aClass.getField("someField");根據下面
MyObject
聲明的someField
字段,
以上的例子將返回Field
實例:public class MyObject{public String someField = null;}如果不存在getField()方法中所給參數名字對應的字段,會拋出
NoSuchFieldException
異常
.5.2字段名稱
一旦獲得一個
Field
實例,可以使用Field.getName()
方法獲取它的字段名字,
如下:Field field = ... //獲取 field 對象String fieldName = field.getName();5.3字段類型
你可以使用
Field.getType()
方法確定一個字段的類型
(
String,int等)
:Field field = aClass.getField("someField");Object fieldType = field.getType();5.4獲取和設置字段值
一旦獲得一個Field引用,可以使用
Field.get()
和Field.set()
方法獲取和設置
它的值,如下:Class aClass = MyObject.classField field = aClass.getField("someField");MyObject objectInstance = new MyObject();Object value = field.get(objectInstance);field.set(objetInstance, value);傳遞給get和set方法的對象實例參數應該是擁有該字段的類的一個實例。在上述的例子中使用了MyObject的一個實例,因為someField是MyObject類的一個實例成員。靜態字段(public static)給get和set方法傳遞null作為參數,而不是以上傳遞的objectInstance參數。
6. 方法
使用Java反射你可以在運行時檢查類的方法并調用它們.這是通過Java類
java.lang.reflect.Method
來實現的
.本章將會更詳細的介紹 JavaMethod
對象.下面是本章所涵蓋的主題:(1) 獲取 Method對象
(2) 方法的參數和返回值類型
(3) 使用Method對象調用方法
6.1獲取Method對象
Method
類是從Class
對象中獲取的.舉例:Class aClass = MyObject.class;Method[] methods = aClass.getMethods();
Method
數組將為類中聲明的每一個
public
的方法保存一個
Method
實例
.如果你知道要訪問方法的確切的參數類型,可以不必獲取方法數組。本示例返回給定類中接受一個字符串作為參數的公共方法”doSomething”。
Class aClass = MyObject.class;Method method = aClass.getMethod("doSomething", new Class[]{String.class});如果沒有方法匹配所給的方法名和參數,在這個例子中是
String.class
,將拋出NoSuchMethodException
異常.如果你想訪問的方法沒有參數,傳遞
null
作為參數類型數組,如下:Class aClass = MyObject.class;Method method = aClass.getMethod("doSomething", null);6.2方法的參數和返回值類型
使用Method對象可以獲取方法的參數,如下:
Method method = ... //獲取 method – 如上Class[] parameterTypes = method.getParameterTypes();亦可以獲取方法的返回值類型,如下:
Method method = ... // obtain method - see aboveClass returnType = method.getReturnType();6.3使用Method對象調用方法
可以像這樣調用方法:
//get method that takes a String as argumentMethod method = MyObject.class.getMethod("doSomething", String.class);Object returnValue = method.invoke(null, "parameter-value1");空參數是你想要調用該方法的對象。如果該方法是靜態的,使用null,而不是一個對象實例。在這個例子中,如果doSomething(String.class)不是靜態的,你需要提供有效的MyObject實例而不是null作為參數;
Method.invoke(Object target, Object ...parameters)
方法接受可變長度的參數,但是你在調用時必須為每一個參數提供一個準確的參量。在這個例子中,方法以字符串作為參數的,所以必須提供一個字符串。7. get和set方法
使用Java反射可以在運行時檢查類的方法并調用它們。這可以用來檢測一個給定的類有哪些get和set方法。可以通過掃描一個類的所有方法并檢查每個方法是否是get或set方法。
下面是一段用來找到類的get和set方法的代碼:
public staticvoid printGetterssetters(Class aClass){
Method[]methods = aClass.getMethods();
for(Methodmethod : methods){
if(isGetter(method))System.out.println("getter: " + method);
if(isSetter(method))System.out.println("setter: " + method);
}
}
public staticboolean isGetter(Method method){
if(!method.getName().startsWith("get")) return false;
if(method.getParameterTypes().length!= 0) return false;
if(void.class.equals(method.getReturnType())return false;
returntrue;
}
public staticboolean isSetter(Method method){
if(!method.getName().startsWith("set"))return false;
if(method.getParameterTypes().length!= 1) return false;
return true;
}
8. 私有字段和方法
可以通過Java反射訪問類的私有字段和方法。
8.1訪問私有字段
要想訪問私有字段你需要調用
Class.getDeclaredField(Stringname)
或Class.getDeclaredFields()
方法.Class.getField(String name)
和Class.getFields()
方法僅返回public字段。下面是一個簡單的示例,類有一個私有字段,代碼通過反射來訪問這個私有字段:public class PrivateObject {private String privateString = null;public PrivateObject(String privateString) {this.privateString = privateString;}}PrivateObject privateObject = new PrivateObject("The Private Value");Field privateStringField = PrivateObject.class. getDeclaredField("privateString");privateStringField.setaccessible(true);String fieldValue = (String) privateStringField.get(privateObject);System.out.println("fieldValue = " + fieldValue);代碼示例輸出"fieldValue = The Private Value",即代碼示例最開始創建的
PrivateObject
實例的私有字段privateString
的值。注意
PrivateObject.class.getDeclaredField("privateString")
的使用
。這個方法僅僅返回特定類聲明的字段,而不包括任何父類中聲明的字段。注意粗體代碼.通過調用Field.setAcessible(true)方法關閉了特定Field實例的訪問檢查,現在通過反射可以訪問它,即使它是私有的,保護的或包范圍,甚至調用者不屬于這些范圍。但編譯器不允許使用普通的代碼該字段,因為僅適用于反射。
8.2訪問私有方法
想要訪問私有方法需要調用
Class.getDeclaredMethod(String name,Class[] parameterTypes)
或Class.getDeclaredMethods()
方法.Class.getMethod(String name, Class[]parameterTypes)
和Class.getMethods()
方法僅返回public方法。下面是一個簡單的示例,類有一個私有方法,代碼通過反射來訪問這個私有方法:public class PrivateObject {private String privateString = null;public PrivateObject(String privateString) {this.privateString = privateString;}private String getPrivateString(){return this.privateString;}}PrivateObject privateObject = new PrivateObject("The Private Value");Method privateStringMethod = PrivateObject.class.getDeclaredMethod("getPrivateString", null);privateStringMethod.setAccessible(true);String returnValue = (String)privateStringMethod.invoke(privateObject, null);System.out.println("returnValue = " + returnValue);代碼示例輸出" returnValue = The Private Value ",即代碼示例最開始創建的
PrivateObject
實例調用的私有方法getPrivateString()
的返回值。注意
PrivateObject.class.getDeclaredMethod("privateString")
方法的使用
。這個方法僅僅返回特定類聲明的方法,而不包括任何父類中聲明的方法。注意粗體代碼.通過調用
Method
.setAcessible(true)方法關閉了特定Method
實例的訪問檢查。現在通過反射可以訪問它,即使它是私有的,保護的或包范圍,甚至調用者不屬于這些范圍。但編譯器不允許使用普通的代碼訪問該方法,因為僅適用于反射。9. Array
在Java反射中使用數組有時候有點棘手,特別是如果你想獲得某種類型的數組的Class對象,如int[]等。本節將討論如何通過Java反射創建數組和Class對象。以下是本章涵蓋的主題:
(1) java.lang.reflect.Array
(2) 創建數組
(3) 訪問數組
(4) 獲取數組的Class對象
(5) 獲取數組的組件類型
9.1 java.lang.reflect.Array
通過Java反射機制使用數組是由thejava.lang.reflect.Array類完成的。不要把這個類和Java集合框架中的java.util.Arrays類相混淆,java.util.Arrays類包含數組排序,將它們轉換為集合等公共方法。
9.2 創建數組
通過Java反射機制創建數組是由
java.lang.reflect.Array
類來完成的,如下:int[] intArray = (int[]) Array.newInstance(int.class, 3);這行代碼創建了一個int數組。
Array.newInstance()
方法的第一個參數告訴我們數組中的元素類型。
第二個參數聲明了數組需要為多少個元素分配空間。9.3 訪問數組
使用Java反射機制可以訪問數組的元素。這是通過
Array.get(...)
和Array.set(...)
方法實現的,如下:int[] intArray = (int[]) Array.newInstance(int.class, 3);Array.set(intArray, 0, 123);Array.set(intArray, 1, 456);Array.set(intArray, 2, 789);System.out.println("intArray[0] = " + Array.get(intArray, 0));System.out.println("intArray[1] = " + Array.get(intArray, 1));System.out.println("intArray[2] = " + Array.get(intArray, 2));這段代碼輸出:
intArray[0] = 123intArray[1] = 456intArray[2] = 7899.4 獲取數組的Class對象
獲取數組的Class對象可以使用無反射的代碼,如下:
Class stringArrayClass = String[].class;使用
Class.forName()
方法
并不易懂。例如,你可以像下面這樣獲取int數組的Class對象:Class intArray = Class.forName("[I");字母
I
在JVM中代表一個。左邊的[
表示它是一個int數組的class。這對其它的基本類型也有效。對于對象,你需要使用一個稍微不同的記號:
Class stringArrayClass = Class.forName("[Ljava.lang.String;");注意到左邊的”
[L
”和右邊的”;
”之間的類名。它代表對象數組的給定類型。另外一個需要注意的是你不能夠使用
Class.forName()
方法獲取基本類型的
Class
對象
。下面兩個例子都會拋出ClassNotFoundException
異常
:Class intClass1 = Class.forName("I");Class intClass2 = Class.forName("int");可以像下面這樣獲取基本數據類型和對象的Class名:
public Class getClass(String className){if("int" .equals(className)) return int .class;if("long".equals(className)) return long.class;...return Class.forName(className);}一旦你獲得某個類型的Class對象,有一個簡單的方法來獲得該類型的數組的Class對象。解決方案是創建所需類型的空數組并從這個空數組獲取Class對象,如下:
Class theClass = getClass(theClassName);Class stringArrayClass = Array.newInstance(theClass, 0).getClass();這提供了一個單一的、統一的方法來訪問任何類型的數組的Class對象。
要確保
Class
對象是一個數組,可以使用Class.isArray()
方法來校驗:Class stringArrayClass = Array.newInstance(String.class, 0).getClass();System.out.println("is array: " + stringArrayClass.isArray());9.5 獲取數組的組件類型
一旦獲取到某個數組的Class對象可以使用
Class.getComponentType()
方法來獲取它的組件類型。組件類型就是數組中元素的類型。例如,int數組的組件類型是int.class Class
對象.String[]
數組的組件類型是java.lang.String
Class
對象。下面是獲取數組的組件類型的示例:
String[] strings = new String[3];Class stringArrayClass = strings.getClass();Class stringArrayComponentType = stringArrayClass.getComponentType();System.out.println(stringArrayComponentType);輸出 "class java.lang.String"即為String數組的組件類型.
10. 注解
使用Java反射你可以在運行時訪問Java類中的注解.下面是本章涵蓋的主題:
(1)什么是Java注解?
(2)類注解
(3)方法注解
(4)參數注解
(5)字段注解
10.1 什么是Java注解?
注解是Java5的一個新功能。注解是一種可以在Java代碼中插入的注釋或元數據。這些注解可以在編譯時由預編譯工具進行處理,也可以在運行時通過Java反射機制來處理。下面是一個類注解的示例:
@MyAnnotation(name="someName", value = "Hello World")public class TheClass {}
TheClass
類的上面有
@MyAnnotation
注解.注解像接口那樣定義。下面是MyAnnotation
注解
的定義:@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface MyAnnotation {public String name();public String value();}在
interface
前面的”@
”標志它是一個注解。一旦你定義了那個注解你就可以在代碼中使用它,就像上面的例子那樣。在注解中定義的兩個指令
@Retention(RetentionPolicy.RUNTIME)
和@Target(ElementType.TYPE)
表明注解是如何使用的
@Retention(RetentionPolicy.RUNTIME)
表明在運行時可以使用反射來訪問這個注解
。如果沒有設置這個指令,在運行時這個注解將不會被保存,因此通過反射訪問不到。
@Target(ElementType.TYPE)
表明注解僅能用于類、接口(包括注釋類型)或枚舉聲明等類型上。你也可以指定METHOD
或FIELD
,或者@Target什么都不指定,這樣它可以用在任何程序元素上。10.2 類注解
可以在運行時獲取類、方法或字段的注解。下面是獲取類注解的示例:
Class aClass = TheClass.class;Annotation[] annotations = aClass.getAnnotations();for(Annotation annotation : annotations){if(annotation instanceof MyAnnotation){MyAnnotation myAnnotation = (MyAnnotation) annotation;System.out.println("name: " + myAnnotation.name());System.out.println("value: " + myAnnotation.value());}}也可以獲取指定的類注解,如下:
Class aClass = TheClass.class;Annotation annotation = aClass.getAnnotation(MyAnnotation.class);if(annotation instanceof MyAnnotation){MyAnnotation myAnnotation = (MyAnnotation) annotation;System.out.println("name: " + myAnnotation.name());System.out.println("value: " + myAnnotation.value());}10.3 方法注解
下面是方法注解的示例:
public class TheClass {@MyAnnotation(name="someName", value = "Hello World")public void doSomething(){}}獲取方法的所有注解,如下:
Method method = ... //obtain method objectAnnotation[] annotations = method.getDeclaredAnnotations();for(Annotation annotation : annotations){if(annotation instanceof MyAnnotation){MyAnnotation myAnnotation = (MyAnnotation) annotation;System.out.println("name: " + myAnnotation.name());System.out.println("value: " + myAnnotation.value());}}獲取方法的特定注解:
Method method = ... // obtain method objectAnnotation annotation = method.getAnnotation(MyAnnotation.class);if(annotation instanceof MyAnnotation){MyAnnotation myAnnotation = (MyAnnotation) annotation;System.out.println("name: " + myAnnotation.name());System.out.println("value: " + myAnnotation.value());}10.4 參數注解
也可以給方法聲明的參數添加注解,如下:
public class TheClass {public static void doSomethingElse(@MyAnnotation(name="aName", value="aValue") String parameter){}}從Method對象獲取參數注解:
Method method = ... //obtain method objectAnnotation[][] parameterAnnotations = method.getParameterAnnotations();Class[] parameterTypes = method.getParameterTypes();int i=0;for(Annotation[] annotations : parameterAnnotations){Class parameterType = parameterTypes[i++];for(Annotation annotation : annotations){if(annotation instanceof MyAnnotation){MyAnnotation myAnnotation = (MyAnnotation) annotation;System.out.println("param: " + parameterType.getName());System.out.println("name : " + myAnnotation.name());System.out.println("value: " + myAnnotation.value());}}}注意
Method.getParameterAnnotations()
方法返回的是二維的Annotation
數組,每個方法參數都有一個一維的Annotation
數組
。10.5 字段注解
下面是字段注解示例:
public class TheClass {@MyAnnotation(name="someName", value = "Hello World")public String myField = null;}獲取字段的所有注解,如下:
Field field = ... //obtain field objectAnnotation[] annotations = field.getDeclaredAnnotations();for(Annotation annotation : annotations){if(annotation instanceof MyAnnotation){MyAnnotation myAnnotation = (MyAnnotation) annotation;System.out.println("name: " + myAnnotation.name());System.out.println("value: " + myAnnotation.value());}}獲取指定字段的注解,如下:
Field field = ... // obtain method objectAnnotation annotation = field.getAnnotation(MyAnnotation.class);if(annotation instanceof MyAnnotation){MyAnnotation myAnnotation = (MyAnnotation) annotation;System.out.println("name: " + myAnnotation.name());System.out.println("value: " + myAnnotation.value());}11. 泛型
經常在文章和論壇里讀到說所有Java泛型信息在編譯的時候被擦除了所以在運行時訪問不到任何泛型信息。這并不完全正確。在極少數的情況下,在運行時是可以訪問泛型信息的。這些情況實際上涵蓋一些我們需要的Java泛型信息。本章將解釋這些情況。下面是本章涵蓋的主題:
(1) 泛型反射的經驗法則
(2) 泛型方法的返回值類型
(3) 泛型方法的參數類型
(4) 泛型字段類型
11.1 泛型反射的經驗法則
使用Java的泛型通常分為兩種不同的情況:
(1)聲明一個可參數化的類/接口。
(2)使用參數化的類。
當寫一個類或接口時,可以指定它應該是可參數化的。就像
java.util.List
接口那樣,可以參數化java.util.List
來創建一個String列表而不是創建Object列表。java.util.List在運行時會檢查參數化的類型,但是沒有辦法知道參數化的是什么類型。這是因為在相同的應用程序中類型可以被參數化為所有的類型。但是,當你檢查方法或字段所聲明使用的參數化類型,你可以在運行時看到可參數化的類型被參數化為什么類型。總之,你不能在運行時看到類型本身被參數化為什么類型,但你可以在使用和參數化它的字段和方法中看到它。
11.2 泛型方法的返回值類型
如果你獲取到一個
java.lang.reflect.Method
對象,可以獲取它的返回值類型信息。下面是一個示例,一個類有一個有參數化返回值類型的方法:public class MyClass {protected List<String> stringList = ...;public List<String> getStringList(){return this.stringList;}}在這個類中可以獲取
getStringList()
方法的泛型返回值類型
。換句話說,可以探測到getStringList()
返回的是
List<String>
而不僅是一個List
。如下:Method method = MyClass.class.getMethod("getStringList", null);Type returnType = method.getGenericReturnType();if(returnType instanceof ParameterizedType){ParameterizedType type = (ParameterizedType) returnType;Type[] typeArguments = type.getActualTypeArguments();for(Type typeArgument : typeArguments){Class typeArgClass = (Class) typeArgument;System.out.println("typeArgClass = " + typeArgClass);}}代碼輸出 "typeArgClass = class java.lang.String"。
Type[]
數組typeArguments
包含一項 -一個代表類java.lang.String
的
Class
實例
。Class
實現了Type
接口。11.3 泛型方法的參數類型
通過Java反射可以在運行時訪問參數類型的泛型類型。下面的示例中,類有一個使用參數化的List作為參數的方法:
public class MyClass {protected List<String> stringList = ...;public void setStringList(List<String> list){this.stringList = list;}}可以訪問方法參數的泛型參數類型,如下:
Method method = Myclass.class.getMethod("setStringList", List.class);Type[] genericParameterTypes = method.getGenericParameterTypes();for(Type genericParameterType : genericParameterTypes){if(genericParameterType instanceof ParameterizedType){ParameterizedType aType = (ParameterizedType) genericParameterType;Type[] parameterArgTypes = aType.getActualTypeArguments();for(Type parameterArgType : parameterArgTypes){Class parameterArgClass = (Class) parameterArgType;System.out.println("parameterArgClass = " + parameterArgClass);}}}代碼輸出"parameterArgType= class java.lang.String"。