Java除了給我們提供在編譯期得到類的各種信息之外,還通過反射讓我們可以在運(yùn)行期間得到類的各種信息。通過反射獲取類的信息,得到類的信息之后,就可以獲取以下相關(guān)內(nèi)容:
- Class對象
- 構(gòu)造器
- 變量
- 方法
- 私有變量與私有方法
- 注解
- 泛型
- 數(shù)組
本文也將從上面幾個(gè)方面來介紹Java反射。本文涉及的所有代碼均在反射代碼
首先放出一個(gè)Java類作為反射的研究對象,類的內(nèi)容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
public abstract class FatherObject implements Runnable{ public void doSomething(){ System.out.println( "做事情......" ); } } public class ExampleObject extends FatherObject{ public int age = 30 ; public String name = "byhieg" ; private Integer score = 60 ; public void printName(){ System.out.println(name); } public int getAge() { return age; } public void setAge( int age) { this .age = age; } public String getName() { return name; } public void setName(String name) { this .name = name; } public Integer getScore() { return score; } public void setScore(Integer score) { this .score = score; } public ExampleObject(){ } public ExampleObject(String name){ } public ExampleObject( int age,Integer score){ } @Override public void doSomething() { super .doSomething(); } @Override public void run() { System.out.println( "run......" ); } } |
Class對象
我們應(yīng)用會用到反射這個(gè)知識點(diǎn),肯定是想要在運(yùn)行時(shí)得到類的信息,根據(jù)類的那些信息去做一些特定的操作。那么,首先無疑就是得到類的信息,在JDK中提供了Class對象來保存類的信息。所以,反射的第一步就是得到Class對象。在JDK中提供了兩種方式得到Class對象。
第一種,如果編寫代碼的時(shí)候,就知道Class的名字,可以直接用如下方式得到Class對象:
Class exampleObjectClass = ExampleObject.class;
第二種,如果在編寫代碼的時(shí)候,不知道類的名字,但是在運(yùn)行時(shí)的時(shí)候,可以得到一個(gè)類名的字符串,可以用如下的方式獲取Class對象:
Class exampleObjectClass = Class.forName("cn.byhieg.reflectiontutorial.ExampleObject");
注意,此方法需要有2個(gè)條件,第一,forName中的字符串必須是全限定名,第二,這個(gè)Class類必須在classpath的路徑下面,因?yàn)樵摲椒〞伋鯟lassNotFoundException的異常。
獲取到這個(gè)Class對象之后,就可以得到類的各種信息,開頭已經(jīng)提及了一些信息,下面,說幾個(gè)沒提到的類的信息。
得到類的名字
類的名字有兩種方式得到,一種是getName(),一種是getSimpleName()。第一種得到的是全限定名,第二種得到的是這個(gè)類的名字,不帶包名。看下面的例子:Class對象,已經(jīng)通過上面的代碼得到了。
1
2
3
4
|
String fullClassName = exampleObjectClass.getName(); String simpleClassName = exampleObjectClass.getSimpleName(); System.out.println(fullClassName); System.out.println(simpleClassName); |
結(jié)果如下:
1
2
|
cn.byhieg.reflectiontutorial.ExampleObject ExampleObject |
得到類的包名、父類和實(shí)現(xiàn)的接口
類的包名和父類,可以通過如下代碼得到。
1
2
3
4
5
6
|
//得到包信息 Package aPackage = exampleObjectClass.getPackage(); System.out.println(aPackage); //得到父類 Class superClass = exampleObjectClass.getSuperclass(); System.out.println(superClass.getSimpleName()); |
結(jié)果如下:
1
2
|
package cn.byhieg.reflectiontutorial FatherObject |
很顯然,得到父類的返回值也是一個(gè)Class對象,那么可以利用這個(gè)對象得到父類的一些信息,比如判斷父類是不是抽象類
System.out.println("父類是不是抽象類 " + Modifier.isAbstract(superClass.getModifiers()));
getModifiers可以得到類的修飾符,從而得到類的修飾符,當(dāng)然,這個(gè)getModifiers不僅僅Class對象可以調(diào)用,Method對象可以調(diào)用。
可以使用java.lang.reflect.Modifier類中的方法來檢查修飾符的類型:
1
2
3
4
5
6
7
8
9
10
11
12
|
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); |
此外,我們還可以得到父類實(shí)現(xiàn)的接口
1
2
3
|
//得到接口 Class[] classes = superClass.getInterfaces(); System.out.println( "父類的接口" + classes[ 0 ]); |
因?yàn)镴ava類可以實(shí)現(xiàn)很多接口,所以用的數(shù)組,但在實(shí)際使用的時(shí)候,需要先判斷數(shù)組的長度。
下面,重點(diǎn)講解上述列出來的內(nèi)容。
構(gòu)造器
利用Java反射可以得到一個(gè)類的構(gòu)造器,并根據(jù)構(gòu)造器,在運(yùn)行時(shí)動(dòng)態(tài)的創(chuàng)建一個(gè)對象。首先,Java通過以下方式獲取構(gòu)造器的實(shí)例:
1
2
3
4
5
|
//構(gòu)造器 Constructor[] constructors = exampleObjectClass.getConstructors(); for (Constructor constructor : constructors){ System.out.println(constructor.toString()); } |
結(jié)果如下:
1
2
3
|
public cn.byhieg.reflectiontutorial.ExampleObject( int ,java.lang.Integer) public cn.byhieg.reflectiontutorial.ExampleObject(java.lang.String) public cn.byhieg.reflectiontutorial.ExampleObject() |
如果,事先知道要訪問的構(gòu)造方法的參數(shù)類型,可以利用如下方法獲取指定的構(gòu)造方法,例子如下:
1
2
|
Constructor constructor = exampleObjectClass.getConstructor(String. class ); System.out.println(constructor.toString()); |
結(jié)果顯然是:
public cn.byhieg.reflectiontutorial.ExampleObject(java.lang.String)
還可以用如下方式得到另一個(gè)構(gòu)造器
1
2
|
Constructor constructor = exampleObjectClass.getConstructor( int . class ,Integer. class ); System.out.println(constructor.toString()); |
此外,如果我們不知道構(gòu)造器的參數(shù),只能得到所有的構(gòu)造器對象,那么可以用如下方式得到每一個(gè)構(gòu)造器對想的參數(shù):
1
2
3
4
5
6
7
8
|
Constructor[] constructors = exampleObjectClass.getConstructors(); for (Constructor constructor : constructors){ Class[] parameterTypes = constructor.getParameterTypes(); System.out.println( "構(gòu)造器參數(shù)如下========================" ); for (Class clz : parameterTypes){ System.out.println( "參數(shù)類型 " + clz.toString()); } } |
結(jié)果如下:
1
2
3
4
5
6
7
8
9
|
構(gòu)造器參數(shù)如下======================== 參數(shù)類型 class java.lang.String 構(gòu)造器參數(shù)如下======================== 參數(shù)類型 int 參數(shù)類型 class java.lang.Integer |
這里,可以看出無參構(gòu)造方法,是不打印出結(jié)果的?;绢愋偷腃lass對象和引用類型的Class對象toString()方法是不一樣的。
現(xiàn)在,可以根據(jù)構(gòu)造器的各種信息,動(dòng)態(tài)創(chuàng)建一個(gè)對象。
1
2
|
Object object = constructor.newInstance( 1 , 100 ); System.out.println(object.toString()); |
這個(gè)創(chuàng)建對象的方式有2個(gè)條件,第一是通過有參構(gòu)造器創(chuàng)建的,第二,構(gòu)造器對象必須通過傳入?yún)?shù)信息的getConstructor得到。
第一個(gè)條件,對于無參構(gòu)造方法就可以創(chuàng)建的對象,不需要得到構(gòu)造器對象,直接Class對象調(diào)用newInstance()方法就直接創(chuàng)建對象。
第二個(gè)條件,構(gòu)造器對象必須通過exampleObjectClass.getConstructor(String.class);這種形式得到。如果通過getConstructors得到構(gòu)造器數(shù)組,然后調(diào)用指定的構(gòu)造器對象去創(chuàng)建對象在JDK1.8是會錯(cuò)的。但是JDK1.6是正常的。
變量
利用Java反射可以在運(yùn)行時(shí)得到一個(gè)類的變量信息,并且可以根據(jù)上面講的方式,創(chuàng)建一個(gè)對象,設(shè)置他的變量值。首先,通過如下方法,得到所有public的變量:
1
2
3
4
|
Field[] fields = exampleObjectClass.getFields(); for (Field field : fields){ System.out.println( "變量為: " + field.toString()); } |
結(jié)果如下:
1
2
|
變量為: public int cn.byhieg.reflectiontutorial.ExampleObject.age 變量為: public java.lang.String cn.byhieg.reflectiontutorial.ExampleObject.name |
很顯然,得到的都是public的變量,上述的private的變量score,并沒有得到。
和構(gòu)造器一樣的得到方式一樣,我們可以指定一個(gè)參數(shù)名,然后得到指定的變量:
1
2
|
Field field = exampleObjectClass.getField( "age" ); System.out.println( "變量為:" + field.toString()); |
上述的變量的toString方法得到的名字太長,Java對Field類提供了getName的方法,返回類中寫的變量名字,上面的代碼就可以改成field.getName()。
反射不僅提供了得到變量的方法,還提供了設(shè)置變量值的方式。通過如下方法可以對一個(gè)動(dòng)態(tài)生成的類,改變其變量值:
1
2
3
4
|
ExampleObject object = ((ExampleObject) constructor1.newInstance( "byhieg" )); System.out.println( "原先的age是 " + object.age); field.set(object, 10 ); System.out.println( "更改之后的age是" + object.age); |
結(jié)果如下:
1
2
|
原先的age是 30 更改之后的age是 10 |
根據(jù)上面的代碼,得到名字為age的Field對象,然后調(diào)用該對象的set方法,傳入一個(gè)對象與要更改的值,就可以改變該對象的值了。注意,此方法不僅僅對成員變量有用,對靜態(tài)變量也可以。當(dāng)然,如果是靜態(tài)變量,傳入null,不用傳對象,也是可以的。
方法
Java反射給我們除了給我們提供類的變量信息之外,當(dāng)然也給我們提供了方法的信息,反射可以讓我們得到方法名,方法的參數(shù),方法的返回類型,以及調(diào)用方法等功能。
首先,通過如下代碼得到方法:
1
2
3
4
5
|
//輸出類的public方法 Method[] methods = exampleObjectClass.getMethods(); for (Method method : methods){ System.out.println( "method = " + method.getName()); } |
和獲取變量一樣似曾相識的代碼,這里直接調(diào)用了getName,來得到類中寫的方法名。寫到這里,大家應(yīng)該自然想到,Java同樣提供了根據(jù)參數(shù),得到具體的方法。
1
2
|
Method method = exampleObjectClass.getMethod( "setAge" , int . class ); System.out.println(method.getName()); |
這里與得到變量不同的是,getMethod方法還需要傳入?yún)?shù)的類型信息,反射提供獲取方法參數(shù)以及返回類型的方法,得到方法參數(shù)的例子如下:
1
2
3
4
5
|
Method method = exampleObjectClass.getMethod( "setAge" , int . class ); System.out.println(method.getName()); for (Class clz : method.getParameterTypes()){ System.out.println( "方法的參數(shù)" + clz.getName()); } |
結(jié)果如下:
1
2
|
setAge 方法的參數(shù) int |
得到方法返回類型的例子如下:
System.out.println(method.getReturnType().getName());
結(jié)果如下:
void
此外,Java反射支持通過invoke調(diào)用得到的方法。例子如下:
method.invoke(exampleObjectClass.newInstance(),1);
invoke第一個(gè)參數(shù)是這個(gè)對象,第二個(gè)參數(shù)是變長數(shù)組,傳入該方法的參數(shù)。和Field對象同樣,對于靜態(tài)方法同樣,可以傳入null,調(diào)用靜態(tài)方法。
私有變量與私有方法
上面的方法只能得到public方法和變量,無法得到非public修飾的方法和變量,Java提供了額外的方法來得到非public變量與方法。即通過getDeclaredFields與getDeclaredMethods方法得到私有的變量與方法,同樣也支持用getDeclaredField(變量名)與getDeclaredMethod(方法名)的形式得到指定的變量名與方法名。但是這樣得到的Field對象與Method對象無法直接運(yùn)用,必須讓這些對象調(diào)用setAccessible(true),才能正常運(yùn)用。之后的方式就可上面講的一樣了。
注解
先寫一個(gè)包含注解的類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@MyAnnotation (name= "byhieg" ,value = "hello world" ) public class AnnotationObject { @MyAnnotation (name= "field" ,value = "變量" ) public String field; @MyAnnotation (name= "method" ,value = "方法" ) public void doSomeThing(){ System.out.println( "做一些事情" ); } public void doOtherThing( @MyAnnotation (name= "param" ,value = "參數(shù)" ) String param){ } } @Retention (RetentionPolicy.RUNTIME) public @interface MyAnnotation { public String name(); public String value(); } |
Java給我們提供了在運(yùn)行時(shí)獲取類的注解信息,可以得到類注解,方法注解,參數(shù)注解,變量注解。
與上面獲取方式一樣,Java提供了2種獲取方式,一種是獲取全部的注解,返回一個(gè)數(shù)組,第二種是指定得到指定的注解。
我們以一個(gè)類注解為例,講解以下這兩種獲取方式。
1
2
3
|
Class clz = AnnotationObject. class ; Annotation[] annotations = clz.getAnnotations(); Annotation annotation = clz.getAnnotation(AnnotationObject. class ); |
然后,就可以根據(jù)得到的注解進(jìn)行后續(xù)的處理,下面是一個(gè)處理的例子:
1
2
3
4
5
6
7
|
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對象調(diào)用getAnnotations得到的,方法注解和變量注解是一樣的,分別用method對象與field對象調(diào)用getDeclaredAnnotations得到注解,沒什么多說的。例子看反射代碼
參數(shù)注解是比較麻煩的一項(xiàng),獲取方式比較得到,第一步,先取得method對象,調(diào)用getParameterAnnotations,但是這個(gè)返回值是一個(gè)二維數(shù)組,因?yàn)閙ethod對象有很多參數(shù),每個(gè)參數(shù)有可能有很多注解。例子如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Method method1 = clz.getMethod( "doOtherThing" ,String. class ); Annotation[][] annotationInParam = method1.getParameterAnnotations(); Class[] params = method1.getParameterTypes(); int i = 0 ; for (Annotation[] annotations: annotationInParam){ Class para = params[i++]; for (Annotation annotation : annotations){ if (annotation instanceof MyAnnotation){ MyAnnotation myAnnotation = (MyAnnotation) annotation; System.out.println( "param: " + para.getName()); System.out.println( "name : " + myAnnotation.name()); System.out.println( "value :" + myAnnotation.value()); } } } |
泛型
因?yàn)镴ava泛型是通過擦除來實(shí)現(xiàn)的,很難直接得到泛型具體的參數(shù)化類型的信息,但是我們可以通過一種間接的形式利用反射得到泛型信息。比如下面這個(gè)類:
1
2
3
4
5
6
7
8
9
|
public class GenericObject { public List<String> lists; public List<String> getLists() { return lists; } public void setLists(List<String> lists) { this .lists = lists; } } |
如果一個(gè)方法返回一個(gè)泛型類,我們可以通過method對象去調(diào)用getGenericReturnType來得到這個(gè)泛型類具體的參數(shù)化類型是什么。看下面的代碼:
1
2
3
4
5
6
7
8
9
10
11
|
Class clz = GenericObject. class ; Method method = clz.getMethod( "getLists" ); Type genericType = method.getGenericReturnType(); if (genericType instanceof ParameterizedType){ ParameterizedType parameterizedType = ((ParameterizedType) genericType); Type[] types = parameterizedType.getActualTypeArguments(); for (Type type : types){ Class actualClz = ((Class) type); System.out.println( "參數(shù)化類型為 : " + actualClz); } } |
結(jié)果如下:
參數(shù)化類型為 : class java.lang.String
步驟有點(diǎn)繁瑣,下面一步步解釋:
- 反射得到返回類型為泛型類的方法
- 調(diào)用getGenericReturnType得到方法返回類型中的參數(shù)化類型
- 判斷該type對象能不能向下轉(zhuǎn)型為ParameterizedType
- 轉(zhuǎn)型成功,調(diào)用getActualTypeArguments得到參數(shù)化類型的數(shù)組,因?yàn)橛械姆盒皖?,不只只有一個(gè)參數(shù)化類型如Map
- 取出數(shù)組中的每一個(gè)的值,轉(zhuǎn)型為Class對象輸出。
看結(jié)果確實(shí)得到了泛型的具體的信息。
如果沒有一個(gè)方法返回泛型類型,那么我們也可以通過方法的參數(shù)為泛型類,來得到泛型的參數(shù)化類型,如上面類中的setLists方法。例子如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Method setMethod = clz.getMethod( "setLists" , List. class ); Type[] genericParameterTypes = setMethod.getGenericParameterTypes(); for (Type genericParameterType: genericParameterTypes){ System.out.println( "GenericParameterTypes為 : " + genericParameterType.getTypeName()); if (genericParameterType instanceof ParameterizedType){ ParameterizedType parameterizedType = ((ParameterizedType) genericParameterType); System.out.println( "ParameterizedType為 :" + parameterizedType.getTypeName()); Type types[] = parameterizedType.getActualTypeArguments(); for (Type type : types){ System.out.println( "參數(shù)化類型為 : " + ((Class) type).getName()); } } } |
執(zhí)行的結(jié)果如下:
1
2
3
|
GenericParameterTypes為 : java.util.List<java.lang.String> ParameterizedType為 :java.util.List<java.lang.String> 參數(shù)化類型為 : java.lang.String |
因?yàn)榉椒ǖ膮?shù)為泛型類型的可能不止一個(gè),所以通過getGenericParameterTypes得到是一個(gè)數(shù)組,我們需要確定每一個(gè)元素,是否是具有參數(shù)化類型。后續(xù)的步驟與上面類似,就不多說了。
如果連方法參數(shù)都不帶泛型類,那么只剩下最后一種情況,通過變量類型,即用Field類。例子如下:
1
2
3
4
5
6
7
8
9
|
Field field = clz.getField( "lists" ); Type type = field.getGenericType(); if (type instanceof ParameterizedType){ ParameterizedType parameterizedType = ((ParameterizedType) type); Type [] types = parameterizedType.getActualTypeArguments(); for (Type type1 : types) { System.out.println( "參數(shù)化類型 : " + ((Class) type1).getTypeName()); } } |
原理和上面的一樣,只不過Type對象是通過field.getGenericType(),剩下的操作類似就不多說了。
關(guān)于通過反射獲取泛型的參數(shù)化類型的信息的介紹就到此為止。
數(shù)組
Java反射可以對數(shù)組進(jìn)行操作,包括創(chuàng)建一個(gè)數(shù)組,訪問數(shù)組中的值,以及得到一個(gè)數(shù)組的Class對象。
下面,先說簡單的,創(chuàng)建數(shù)組以及訪問數(shù)組中的值:在反射中使用Array這個(gè)類,是reflect包下面的。
1
2
3
4
5
6
7
8
9
10
|
//創(chuàng)建一個(gè)int類型的數(shù)組,長度為3 int [] intArray = ( int [])Array.newInstance( int . class , 3 ); //通過反射的形式,給數(shù)組賦值 for ( int i = 0 ;i < intArray.length;i++){ Array.set(intArray,i,i + 2 ); } //通過反射的形式,得到數(shù)組中的值 for ( int i = 0 ; i < intArray.length;i++){ System.out.println(Array.get(intArray,i)); } |
上述就是創(chuàng)建數(shù)組,訪問數(shù)組中的值利用反射方式。
對于得到一個(gè)數(shù)組的Class對象,簡單的可以用int[].class,或者利用Class.forName的形式得到,寫法比較奇怪:
1
2
|
Class clz = Class.forName( "[I" ); System.out.println(clz.getTypeName()); |
結(jié)果為:
int[]
這個(gè)forName中的字符串,[ 表示是數(shù)組,I 表示是int,float就是 F,double就是 D 等等,如果要得到一個(gè)普通對象的數(shù)組,則用下面的形式:
Class stringClz = Class.forName("[Ljava.lang.String;");
[ 表示是數(shù)組, L 的右邊是類名,類型的右邊是一個(gè);;
這種方式獲取數(shù)組的Class對象實(shí)在是太繁瑣了。
在得到數(shù)組的Class對象之后,就可以調(diào)用他的一些獨(dú)特的方法,比如調(diào)用getComponentType來得到數(shù)組成員的類型信息,如int數(shù)組就是成員類型就是int。
System.out.println(clz.getComponentType().getTypeName());
結(jié)果為int
總結(jié)
這次,關(guān)于反射的各種應(yīng)用就到此為止,后續(xù)可能會有深入的知識講解。具體的代碼可以去看反射代碼
在src包里面是各種類,在test類里是對這些類的訪問。
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時(shí)也希望多多支持服務(wù)器之家!
原文鏈接:http://www.cnblogs.com/qifengshi/p/6267511.html