Java反射
概述
- Java反射機(jī)制可以讓我們?cè)诰幾g期(Compile Time)之外的運(yùn)行期(Runtime)檢查類慧库,接口,變量以及方法的信息虑啤。
- 反射還可以讓我們?cè)谶\(yùn)行期實(shí)例化對(duì)象,調(diào)用方法,通過(guò)調(diào)用get/set方法獲取變量的值, 即使方法或字段是私有的的也可以通過(guò)反射的形式調(diào)用
使用場(chǎng)景:
- 在某些情況下酪捡,我們要使用的類在運(yùn)行時(shí)才能確定,但是這個(gè)類符合某種特定的規(guī)范纳账,例如JDBC逛薇。因?yàn)槲覀儫o(wú)法在編譯期就使用它,所以只能通過(guò)反射來(lái)使用運(yùn)行時(shí)才存在的類疏虫。
- 我們對(duì)于類的內(nèi)部信息不可知永罚,必須要等到運(yùn)行時(shí)才能獲取類的具體信息啤呼。比如ORM框架,在運(yùn)行時(shí)才能夠獲取類中的各個(gè)字段呢袱,然后通過(guò)反射的形式獲取其字段名和值官扣,存入數(shù)據(jù)庫(kù)。
- 注解相關(guān)羞福。例如JUnit惕蹄,使用反射來(lái)判斷類中的方法是否有
@Test
注解,來(lái)運(yùn)行單元測(cè)試治专。
Class對(duì)象
當(dāng)我們編寫(xiě)完一個(gè)Java項(xiàng)目之后,所有的Java文件都會(huì)被編譯成一個(gè).class文件卖陵,這些Class對(duì)象承載了這個(gè)類型的父類、接口看靠、構(gòu)造函數(shù)赶促、方法、成員變量等原始信息挟炬,這些class文件在程序運(yùn)行時(shí)會(huì)被ClassLoader加載到虛擬機(jī)中鸥滨。當(dāng)一個(gè)類被加載以后,Java虛擬機(jī)就會(huì)在內(nèi)存中自動(dòng)產(chǎn)生一個(gè)Class對(duì)象谤祖。我們通過(guò)new的形式創(chuàng)建對(duì)象實(shí)際上就是通過(guò)這些Class來(lái)創(chuàng)建,只是這個(gè)過(guò)程對(duì)于我們是不透明的而已婿滓。
在你想檢查一個(gè)類的信息之前,你首先需要獲取類的Class對(duì)象粥喜。Java中的所有類型包括基本類型(int, long, float等等)凸主,即使是數(shù)組都有與之關(guān)聯(lián)的Class類的對(duì)象。有三種方式來(lái)獲取一個(gè)類的Class對(duì)象额湘。
- 如果在編譯期就知道類的名稱卿吐,可以使用類字面量(class-literal)來(lái)獲取Class對(duì)象,
Foo.class
锋华。
Class myObjectClass = MyObject.class;
- 如果已經(jīng)得到一個(gè)對(duì)象嗡官,可以通過(guò)對(duì)象的
object.getClass()
方法獲取Class對(duì)象。
TestClass testClass = new TestClass();
Class<?> clazz = testClass.getClass();
- 如果在編譯期獲取不到目標(biāo)類型毯焕,但是在知道它的完整類路徑(全類名)衍腥,那么可以通過(guò)
Class.forName()
方法來(lái)獲取Class對(duì)象。
Class<?> clazz = Class.forName("com.example.TestClass");
Class對(duì)象的一些方法
基本方法
-
String name = clazz.getName()
獲取類的全類名(包括包信息) -
String simpleName = clazz.getSimpleName()
獲取類名 -
Package package = clazz.getPackage()
獲取包信息 -
Class superClass = clazz.getSuperclass()
獲取父類的Class對(duì)象 -
Class[] interfaces = clazz.getInterfaces()
獲取類所實(shí)現(xiàn)的接口集合
getInterfaces()
返回的僅僅只是當(dāng)前類所實(shí)現(xiàn)的接口纳猫,不包括父類實(shí)現(xiàn)的接口 -
int modifiers = clazz.getModifiers()
返回類修飾符
每個(gè)修飾符都是一個(gè)位標(biāo)識(shí)(flag bit)婆咸,將這些修飾符封裝成一個(gè)int類型的值∥咴可以通過(guò)java.lang.Modifier
類中的方法來(lái)檢查修飾符的類型尚骄。
或者可以通過(guò)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);
Modifier.toString(int mod)
靜態(tài)方法輸出類修飾符物遇。
構(gòu)造器
Constructor<?>[] getConstructors()
Constructor<T> getConstructor(Class<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors()
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
通過(guò)方法簽名應(yīng)該很容易看出來(lái)憾儒,有參數(shù)的方法是通過(guò)構(gòu)造器的參數(shù)類型獲取唯一的構(gòu)造器乃沙,沒(méi)有參數(shù)的是獲取類所有的構(gòu)造函數(shù)起趾。帶有Declared
的是獲取類自身所有的構(gòu)造函數(shù),public训裆、default蜀铲、protected和private的边琉。而不帶Declared
則只返回public的構(gòu)造函數(shù)。
方法
Method[] getMethods()
Method getMethod(String name, Class<?>... parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
和獲取構(gòu)造函數(shù)的方法類似记劝,不過(guò)帶有Declared
的是獲取自身所有的方法变姨,不包括從父類中繼承的厌丑。而不帶Declared
的則返回該類所有public的方法,包括從父類中繼承下來(lái)的砍鸠。其中耕驰,參數(shù)name
為方法的名稱,parameterTypes
為參數(shù)的類型朦肘。
成員變量
Field[] getFields()
Field getField(String name)
Field[] getDeclaredFields()
Field getDeclaredField(String name)
這個(gè)和獲取方法的差不多,帶有Declared
的是獲取自身所有的成員變量示启,不包括從父類中繼承的领舰。而不帶Declared
的則返回該類所有public的成員變量迟螺,包括從父類中繼承下來(lái)的。其中矩父,參數(shù)name
為成員變量的名稱。
注解
Annotation[] getAnnotations()
<A extends Annotation> A getAnnotation(Class<A> annotationClass)
Annotation[] getDeclaredAnnotations()
<A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass)
和獲取方法的差不多民轴。annotationClass
為注解的Class對(duì)象。注意瑰钮,getDeclaredAnnotation(Class<A> annotationClass)
方法是Java8新添加的方法
Constructor對(duì)象
利用Java的反射機(jī)制你可以檢查一個(gè)類的構(gòu)造方法微驶,并且可以在運(yùn)行期創(chuàng)建一個(gè)對(duì)象浪谴。這些功能都是通過(guò)java.lang.reflect.Constructor這個(gè)類實(shí)現(xiàn)的因苹。
首先展示我們測(cè)試用的類。
public class Student {
@MyAnnotation(name = "annotation")
private int age;
private List<String> parent;
public Student(int age) {
this.age = age;
}
public int getAge() {
return age;
}
@MyAnnotation(name = "annotation")
public void setAge(@MyAnnotation(name = "annotation") int age) {
this.age = age;
}
public List<String> getParent() {
return parent;
}
public void setParent(List<String> parent) {
this.parent = parent;
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String name();
}
利用Contructor對(duì)象實(shí)例化一個(gè)類
方法定義:T newInstance(Object ... initargs)
代碼如下:
Class<Student> clazz = Student.class;
// 獲得一個(gè)Contructor對(duì)象扶檐,參數(shù)類型為int
Constructor<Student> constructor = clazz.getConstructor(int.class);
int age = 5;
// 利用Contructor對(duì)象示例化一個(gè)實(shí)例
Student student = constructor.newInstance(age);
System.out.println("student's age is :" + student.getAge()); // 打印出 student's age is :5
Method對(duì)象
使用Java反射你可以在運(yùn)行期檢查一個(gè)方法的信息以及在運(yùn)行期調(diào)用這個(gè)方法款筑,通過(guò)使用java.lang.reflect.Method類就可以實(shí)現(xiàn)上述功能。
通過(guò)Method對(duì)象調(diào)用方法
方法定義:Object invoke(Object obj, Object... args)
參數(shù)obj
為調(diào)用方法的實(shí)例對(duì)象醋虏,如果方法是靜態(tài)方法,則obj
傳null
颈嚼。參數(shù)args
是原方法的參數(shù)。
如果方法的返回值為void
叫挟,則invoke
的返回值為null
限煞。
代碼如下:
Method setMethod = clazz.getMethod("setAge", int.class);
int anotherAge = 20;
// 通過(guò)invoke調(diào)用方法,參數(shù)1為對(duì)象實(shí)例署驻,如果該方法為靜態(tài)方法,則傳null瓶蚂。接下來(lái)的參數(shù)是原方法的參數(shù)宣吱。
setMethod.invoke(student, anotherAge);
Method getMethod = clazz.getMethod("getAge");
Object result = getMethod.invoke(student);
System.out.println("student's age is :" + result); // 打印出 student's age is :20
通過(guò)Method對(duì)象獲取方法參數(shù)以及返回類型
-
Class<?>[] getParameterTypes()
獲取方法所有的參數(shù)類型 -
Class<?> getReturnType()
獲取方法的返回類型
代碼如下:
// 獲取方法所有的參數(shù)類型
Class[] parameterTypes = setMethod.getParameterTypes();
for (Class parameterType : parameterTypes) {
System.out.println(parameterType); // 打印出int
}
// 獲取方法的返回類型
Class returnType = setMethod.getReturnType();
System.out.println(returnType); // 打印出void
通過(guò)Method對(duì)象獲取泛型參數(shù)類型和泛型返回類型
-
Type getGenericReturnType()
獲取泛型返回類型 -
Type[] getGenericParameterTypes()
獲取泛型參數(shù)類型
上文說(shuō)的getReturnType
和getParameterTypes
返回的值原始類型(raw type),無(wú)法獲得泛型類型杭攻。而getGenericReturnType
和getGenericParameterTypes
可以返回參數(shù)化類型。當(dāng)然兆解,如果方法本身返回的不是參數(shù)化類型,那么這兩個(gè)方法和getReturnType
等效果相同叼架。
首先衣撬,我們先科普一下Type
接口。
Type is the common superinterface for all types in the Java
programming language. These include raw types, parameterized types,
array types, type variables and primitive types.
- raw types : 例如 List
- parameterized types : 例如
List<String>
- array types : 例如 String[]
- type variables : 例如
interface List<E>
中的E - primitive types : 例如 int
然后我們來(lái)看一下代碼:
Method getParentMethod = clazz.getMethod("getParent");
Type returnType = getParentMethod.getGenericReturnType();
if (returnType instanceof ParameterizedType) {
// 將returnType轉(zhuǎn)化為子類ParameterizedType具练。此時(shí)的parameterizedType為L(zhǎng)ist<String>
ParameterizedType parameterizedType = (ParameterizedType) returnType;
// 通過(guò)getActualTypeArguments方法獲取泛型類型。此時(shí)為String
Type[] typeArgs = parameterizedType.getActualTypeArguments();
for (Type typeArg : typeArgs) {
System.out.println(typeArg); // 打印出 String
}
}
上述代碼以getGenericReturnType
方法為例哥遮,getGenericParameterTypes
方法是類似的陵究。通過(guò)getGenericReturnType
返回的是整個(gè)參數(shù)化類型,如代碼中的List<String>
铜邮。通過(guò)ParameterizedType
的getActualTypeArguments
方法返回的才是泛型里的參數(shù)類型,如List<String>
中的String
扔茅。
通過(guò)Method對(duì)象獲取方法注解
Annotation[] getAnnotations()
<T extends Annotation> T getAnnotation(Class<T> annotationClass)
Annotation[] getDeclaredAnnotations()
獲取方法注解和獲取類注解類似秸苗,下面我們看一下示例:
Class<Student> clazz = Student.class;
Method method = clazz.getDeclaredMethod("setAge", int.class);
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
System.out.println(annotation.name()); // 打印出annotation
通過(guò)Method對(duì)象獲取參數(shù)注解
-
Annotation[][] getParameterAnnotations()
返回一個(gè)二維數(shù)組,每一個(gè)方法的參數(shù)包含一個(gè)注解數(shù)組
示例代碼如下:
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
// 遍歷獲得各個(gè)參數(shù)的注解數(shù)組
for (Annotation[] annotations : parameterAnnotations) {
// 遍歷注解數(shù)組玖瘸,獲取每個(gè)注解
for (Annotation annotation : annotations) {
if (annotation instanceof MyAnnotation) {
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println(myAnnotation.name()); // 打印出annotation
}
}
}
Field對(duì)象
使用Java反射機(jī)制你可以運(yùn)行期檢查一個(gè)類的變量信息(成員變量)或者獲取或者設(shè)置變量的值檀咙。通過(guò)使用java.lang.reflect.Field類就可以實(shí)現(xiàn)上述功能。
獲取或設(shè)置(get/set)變量值
-
Object get(Object obj)
獲取成員變量的值攀芯,參數(shù)obj
為對(duì)象實(shí)例文虏,如果是靜態(tài)成員變量殖演,則傳入null
即可年鸳。 -
void set(Object obj, Object value)
設(shè)置成員變量的值,參數(shù)obj
為對(duì)象實(shí)例彼棍,如果是靜態(tài)成員變量膳算,則傳入null
即可。value
為欲設(shè)置的值涕蜂。
除了上訴兩個(gè)方法,Field
類還提供了獲取和設(shè)置基礎(chǔ)類型的方法蜘拉。例如setBoolean
有鹿、getBoolean
等方法。
代碼示例如下:
Class<Student> clazz = Student.class;
Field ageField = clazz.getDeclaredField("age");
Student student = new Student(15);
// 通過(guò)get方法獲取成員變量的值
Object age = ageField.get(student);
System.out.println(age); // 輸出15
// 通過(guò)set方法設(shè)置成員變量的值
ageField.set(student, 20);
System.out.println(ageField.get(student)); // 輸出20
通過(guò)Field對(duì)象獲取成員變量的泛型參數(shù)類型
-
Type getGenericType()
獲取帶泛型參數(shù)的成員變量類型(即List<String>,而不僅僅是raw type)
和Method對(duì)象的方法類似持寄,獲取的Type是ParameterizedType年局,強(qiáng)轉(zhuǎn)之后,通過(guò)ParameterizedType的getActualTypeArguments()
方法可以獲取成員變量實(shí)際的泛型參數(shù)類型矢否。
代碼示例如下:
Class<Student> clazz = Student.class;
Field parentField = clazz.getDeclaredField("parent");
Type type = parentField.getGenericType();
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] typeArgs = parameterizedType.getActualTypeArguments();
for (Type typeArg : typeArgs) {
System.out.println(typeArg); // 輸出class java.lang.String
}
}
通過(guò)Field對(duì)象獲取注解
Annotation[] getAnnotations()
<T extends Annotation> T getAnnotation(Class<T> annotationClass)
Annotation[] getDeclaredAnnotations()
和Method的方法類似僵朗。示例如下:
Field ageField = clazz.getDeclaredField("age");
MyAnnotation myAnnotation = ageField.getAnnotation(MyAnnotation.class);
System.out.println(myAnnotation.name()); // 打印出annotation
AnnotatedElement接口
上文中我們介紹了通過(guò)Class
,Constructor
,Method
,Field
來(lái)獲取注解,可以觀察到他們調(diào)用的方法都是相同的验庙,因?yàn)樗麄兌紝?shí)現(xiàn)了AnnotatedElement
接口。
首先我們看一下文檔:
Represents an annotated element of the program currently running in this
VM. This interface allows annotations to be read reflectively. All
annotations returned by methods in this interface are immutable and
serializable. The arrays returned by methods of this interface may be modified
by callers without affecting the arrays returned to other callers.
該接口表示當(dāng)前運(yùn)行在VM的程序中悴了,被注解標(biāo)注的元素,這就要求該注解的Retention
是RetentionPolicy.RUNTIME
湃交。當(dāng)然這個(gè)接口位于java.lang.reflect
包中,所以當(dāng)然是為了反射而存在的咯息罗。而且接口中方法返回的注解都是不可變的(immutable)才沧,所以不用擔(dān)心因?yàn)樽⒔庠谶\(yùn)行時(shí)被改變影響到其它代碼。
接下來(lái)我們看一下接口的幾個(gè)方法:
-
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
: 判斷該類型的注解是否存在温圆。 -
<T extends Annotation> T getAnnotation(Class<T> annotationClass)
: 根據(jù)注解類型返回該AnnotatedElement
上的注解捌木,如果不存在,則返回null
刨裆。 -
Annotation[] getAnnotations()
: 返回所有的注解,如果沒(méi)有注解存在帆啃,則返回空數(shù)組。 -
Annotation[] getDeclaredAnnotations()
: 返回所有的注解诽偷,不包括從父類中繼承來(lái)的疯坤,如果注解不存在,則返回空數(shù)組压怠。
Java8以前的注解不允許重復(fù)出現(xiàn)相同類型的注解,但是Java8可以通過(guò)元注解@Repeatable
來(lái)表示一個(gè)注解是可重復(fù)出現(xiàn)的蜗顽,所以AnnotatedElement
接口又添加了幾個(gè)和重復(fù)注解相關(guān)的方法雨让。
-
<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)
: 返回所有同一種類型的注解。如果不存在崔挖,則返回空數(shù)組贸街。 -
<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)
: 返回所有同一種類型的注解狸相,不包括從父類繼承來(lái)的卷哩。如果不存在,則返回空數(shù)組将谊。 -
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)
: 根據(jù)注解類型返回注解渐白,不包括從父類中繼承來(lái)的。如果不存在纯衍,返回null
。這個(gè)和重復(fù)注解沒(méi)關(guān)系瓦堵,但也是Java8中添加的新方法歌亲。