Java除了給咱們提供在編譯期獲得類的各類信息以外,還經過反射讓咱們能夠在運行期間獲得類的各類信息。經過反射獲取類的信息,獲得類的信息以後,就能夠獲取如下相關內容:java
本文也將從上面幾個方面來介紹Java反射。本文涉及的全部代碼均在反射代碼
首先放出一個Java類做爲反射的研究對象,類的內容以下:git
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......"); } }
咱們應用會用到反射這個知識點,確定是想要在運行時獲得類的信息,根據類的那些信息去作一些特定的操做。那麼,首先無疑就是獲得類的信息,在JDK中提供了Class對象來保存類的信息。因此,反射的第一步就是獲得Class對象。在JDK中提供了兩種方式獲得Class對象。
第一種,若是編寫代碼的時候,就知道Class的名字,能夠直接用以下方式獲得Class對象:github
Class exampleObjectClass = ExampleObject.class;
第二種,若是在編寫代碼的時候,不知道類的名字,可是在運行時的時候,能夠獲得一個類名的字符串,能夠用以下的方式獲取Class對象:數組
Class exampleObjectClass = Class.forName("cn.byhieg.reflectiontutorial.ExampleObject");
注意,此方法須要有2個條件,第一,forName中的字符串必須是全限定名,第二,這個Class類必須在classpath的路徑下面,由於該方法會拋出ClassNotFoundException
的異常。ide
獲取到這個Class對象以後,就能夠獲得類的各類信息,開頭已經說起了一些信息,下面,說幾個沒提到的類的信息。this
類的名字有兩種方式獲得,一種是getName(),一種是getSimpleName()。第一種獲得的是全限定名,第二種獲得的是這個類的名字,不帶包名。看下面的例子:Class對象,已經經過上面的代碼獲得了。spa
String fullClassName = exampleObjectClass.getName(); String simpleClassName = exampleObjectClass.getSimpleName(); System.out.println(fullClassName); System.out.println(simpleClassName);
結果以下:code
cn.byhieg.reflectiontutorial.ExampleObject ExampleObject
類的包名和父類,能夠經過以下代碼獲得。對象
//獲得包信息 Package aPackage = exampleObjectClass.getPackage(); System.out.println(aPackage); //獲得父類 Class superClass = exampleObjectClass.getSuperclass(); System.out.println(superClass.getSimpleName());
結果以下:接口
package cn.byhieg.reflectiontutorial FatherObject
很顯然,獲得父類的返回值也是一個Class對象,那麼能夠利用這個對象獲得父類的一些信息,好比判斷父類是否是抽象類
System.out.println("父類是否是抽象類 " + Modifier.isAbstract(superClass.getModifiers()));
getModifiers能夠獲得類的修飾符,從而獲得類的修飾符,固然,這個getModifiers不只僅Class對象能夠調用,Method對象能夠調用。
可使用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);
此外,咱們還能夠獲得父類實現的接口
//獲得接口 Class[] classes = superClass.getInterfaces(); System.out.println("父類的接口" + classes[0]);
由於Java類能夠實現不少接口,因此用的數組,但在實際使用的時候,須要先判斷數組的長度。
下面,重點講解上述列出來的內容。
利用Java反射能夠獲得一個類的構造器,並根據構造器,在運行時動態的建立一個對象。首先,Java經過如下方式獲取構造器的實例:
//構造器 Constructor[] constructors = exampleObjectClass.getConstructors(); for (Constructor constructor : constructors){ System.out.println(constructor.toString()); }
結果以下:
public cn.byhieg.reflectiontutorial.ExampleObject(int,java.lang.Integer) public cn.byhieg.reflectiontutorial.ExampleObject(java.lang.String) public cn.byhieg.reflectiontutorial.ExampleObject()
若是,事先知道要訪問的構造方法的參數類型,能夠利用以下方法獲取指定的構造方法,例子以下:
Constructor constructor = exampleObjectClass.getConstructor(String.class); System.out.println(constructor.toString());
結果顯然是:
public cn.byhieg.reflectiontutorial.ExampleObject(java.lang.String)
還能夠用以下方式獲得另外一個構造器
Constructor constructor = exampleObjectClass.getConstructor(int.class,Integer.class); System.out.println(constructor.toString());
此外,若是咱們不知道構造器的參數,只能獲得全部的構造器對象,那麼能夠用以下方式獲得每個構造器對想的參數:
Constructor[] constructors = exampleObjectClass.getConstructors(); for (Constructor constructor : constructors){ Class[] parameterTypes = constructor.getParameterTypes(); System.out.println("構造器參數以下========================"); for (Class clz : parameterTypes){ System.out.println("參數類型 " + clz.toString()); } }
結果以下:
構造器參數以下======================== 參數類型 class java.lang.String 構造器參數以下======================== 參數類型 int 參數類型 class java.lang.Integer
這裏,能夠看出無參構造方法,是不打印出結果的。基本類型的Class對象和引用類型的Class對象toString()方法是不同的。
如今,能夠根據構造器的各類信息,動態建立一個對象。
Object object = constructor.newInstance(1,100); System.out.println(object.toString());
這個建立對象的方式有2個條件,第一是經過有參構造器建立的,第二,構造器對象必須經過傳入參數信息的getConstructor獲得。
第一個條件,對於無參構造方法就能夠建立的對象,不須要獲得構造器對象,直接Class對象調用newInstance()方法就直接建立對象。
第二個條件,構造器對象必須經過exampleObjectClass.getConstructor(String.class);
這種形式獲得。若是經過getConstructors
獲得構造器數組,而後調用指定的構造器對象去建立對象在JDK1.8是會錯的。可是JDK1.6是正常的。
利用Java反射能夠在運行時獲得一個類的變量信息,而且能夠根據上面講的方式,建立一個對象,設置他的變量值。首先,經過以下方法,獲得全部public的變量:
Field[] fields = exampleObjectClass.getFields(); for (Field field : fields){ System.out.println("變量爲: " + field.toString()); }
結果以下:
變量爲: public int cn.byhieg.reflectiontutorial.ExampleObject.age 變量爲: public java.lang.String cn.byhieg.reflectiontutorial.ExampleObject.name
很顯然,獲得的都是public的變量,上述的private的變量score,並無獲得。
和構造器同樣的獲得方式同樣,咱們能夠指定一個參數名,而後獲得指定的變量:
Field field = exampleObjectClass.getField("age"); System.out.println("變量爲:" + field.toString());
上述的變量的toString方法獲得的名字太長,Java對Field類提供了getName的方法,返回類中寫的變量名字,上面的代碼就能夠改爲field.getName()。
反射不只提供了獲得變量的方法,還提供了設置變量值的方式。經過以下方法能夠對一個動態生成的類,改變其變量值:
ExampleObject object = ((ExampleObject) constructor1.newInstance("byhieg")); System.out.println("原先的age是 " + object.age); field.set(object,10); System.out.println("更改以後的age是" + object.age);
結果以下:
原先的age是 30 更改以後的age是10
根據上面的代碼,獲得名字爲age的Field對象,而後調用該對象的set方法,傳入一個對象與要更改的值,就能夠改變該對象的值了。注意,此方法不只僅對成員變量有用,對靜態變量也能夠。固然,若是是靜態變量,傳入null,不用傳對象,也是能夠的。
Java反射給咱們除了給咱們提供類的變量信息以外,固然也給咱們提供了方法的信息,反射可讓咱們獲得方法名,方法的參數,方法的返回類型,以及調用方法等功能。
首先,經過以下代碼獲得方法:
//輸出類的public方法 Method[] methods = exampleObjectClass.getMethods(); for (Method method : methods){ System.out.println("method = "+ method.getName()); }
和獲取變量同樣似曾相識的代碼,這裏直接調用了getName,來獲得類中寫的方法名。寫到這裏,你們應該天然想到,Java一樣提供了根據參數,獲得具體的方法。
Method method = exampleObjectClass.getMethod("setAge",int.class); System.out.println(method.getName());
這裏與獲得變量不一樣的是,getMethod方法還須要傳入參數的類型信息,反射提供獲取方法參數以及返回類型的方法,獲得方法參數的例子以下:
Method method = exampleObjectClass.getMethod("setAge",int.class); System.out.println(method.getName()); for (Class clz : method.getParameterTypes()){ System.out.println("方法的參數" + clz.getName()); }
結果以下:
setAge 方法的參數int
獲得方法返回類型的例子以下:
System.out.println(method.getReturnType().getName());
結果以下:
void
此外,Java反射支持經過invoke調用獲得的方法。例子以下:
method.invoke(exampleObjectClass.newInstance(),1);
invoke第一個參數是這個對象,第二個參數是變長數組,傳入該方法的參數。和Field對象一樣,對於靜態方法一樣,能夠傳入null,調用靜態方法。
上面的方法只能獲得public方法和變量,沒法獲得非public修飾的方法和變量,Java提供了額外的方法來獲得非public變量與方法。即經過getDeclaredFields
與getDeclaredMethods
方法獲得私有的變量與方法,一樣也支持用getDeclaredField(變量名)
與getDeclaredMethod(方法名)
的形式獲得指定的變量名與方法名。可是這樣獲得的Field對象與Method對象沒法直接運用,必須讓這些對象調用setAccessible(true),才能正常運用。以後的方式就可上面講的同樣了。
先寫一個包含註解的類:
@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 = "參數") String param){ } } @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { public String name(); public String value(); }
Java給咱們提供了在運行時獲取類的註解信息,能夠獲得類註解,方法註解,參數註解,變量註解。
與上面獲取方式同樣,Java提供了2種獲取方式,一種是獲取所有的註解,返回一個數組,第二種是指定獲得指定的註解。
咱們以一個類註解爲例,講解如下這兩種獲取方式。
Class clz = AnnotationObject.class; Annotation[] annotations = clz.getAnnotations(); Annotation annotation = clz.getAnnotation(AnnotationObject.class);
而後,就能夠根據獲得的註解進行後續的處理,下面是一個處理的例子:
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對象調用getAnnotations
獲得的,方法註解和變量註解是同樣的,分別用method對象與field對象調用getDeclaredAnnotations
獲得註解,沒什麼多說的。例子看反射代碼
參數註解是比較麻煩的一項,獲取方式比較獲得,第一步,先取得method對象,調用getParameterAnnotations
,可是這個返回值是一個二維數組,由於method對象有不少參數,每一個參數有可能有不少註解。例子以下:
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()); } } }
由於Java泛型是經過擦除來實現的,很難直接獲得泛型具體的參數化類型的信息,可是咱們能夠經過一種間接的形式利用反射獲得泛型信息。好比下面這個類:
public class GenericObject { public List<String> lists; public List<String> getLists() { return lists; } public void setLists(List<String> lists) { this.lists = lists; } }
若是一個方法返回一個泛型類,咱們能夠經過method對象去調用getGenericReturnType
來獲得這個泛型類具體的參數化類型是什麼。看下面的代碼:
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("參數化類型爲 : " + actualClz); } }
結果以下:
參數化類型爲 : class java.lang.String
步驟有點繁瑣,下面一步步解釋:
getGenericReturnType
獲得方法返回類型中的參數化類型ParameterizedType
getActualTypeArguments
獲得參數化類型的數組,由於有的泛型類,不僅只有一個參數化類型如Map<K,V>看結果確實獲得了泛型的具體的信息。
若是沒有一個方法返回泛型類型,那麼咱們也能夠經過方法的參數爲泛型類,來獲得泛型的參數化類型,如上面類中的setLists方法。例子以下:
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("參數化類型爲 : " + ((Class) type).getName()); } } }
執行的結果以下:
GenericParameterTypes爲 : java.util.List<java.lang.String> ParameterizedType爲 :java.util.List<java.lang.String> 參數化類型爲 : java.lang.String
由於方法的參數爲泛型類型的可能不止一個,因此經過getGenericParameterTypes
獲得是一個數組,咱們須要肯定每個元素,是不是具備參數化類型。後續的步驟與上面相似,就很少說了。
若是連方法參數都不帶泛型類,那麼只剩下最後一種狀況,經過變量類型,即用Field類。例子以下:
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("參數化類型 : " + ((Class) type1).getTypeName()); } }
原理和上面的同樣,只不過Type對象是經過field.getGenericType()
,剩下的操做相似就很少說了。
關於經過反射獲取泛型的參數化類型的信息的介紹就到此爲止。
Java反射能夠對數組進行操做,包括建立一個數組,訪問數組中的值,以及獲得一個數組的Class對象。
下面,先說簡單的,建立數組以及訪問數組中的值:在反射中使用Array這個類,是reflect包下面的。
//建立一個int類型的數組,長度爲3 int[] intArray = (int[])Array.newInstance(int.class,3); //經過反射的形式,給數組賦值 for (int i = 0 ;i < intArray.length;i++){ Array.set(intArray,i,i + 2); } //經過反射的形式,獲得數組中的值 for (int i = 0 ; i < intArray.length;i++){ System.out.println(Array.get(intArray,i)); }
上述就是建立數組,訪問數組中的值利用反射方式。
對於獲得一個數組的Class對象,簡單的能夠用int[].class
,或者利用Class.forName的形式獲得,寫法比較奇怪:
Class clz = Class.forName("[I"); System.out.println(clz.getTypeName());
結果爲:
int[]
這個forName中的字符串,[
表示是數組,I
表示是int,float就是F
,double就是D
等等,若是要獲得一個普通對象的數組,則用下面的形式:
Class stringClz = Class.forName("[Ljava.lang.String;");
[
表示是數組,L
的右邊是類名,類型的右邊是一個;
;
這種方式獲取數組的Class對象實在是太繁瑣了。
在獲得數組的Class對象以後,就能夠調用他的一些獨特的方法,好比調用getComponentType
來獲得數組成員的類型信息,如int數組就是成員類型就是int。
System.out.println(clz.getComponentType().getTypeName());
結果爲int
此次,關於反射的各類應用就到此爲止,後續可能會有深刻的知識講解。具體的代碼能夠去看反射代碼 在src包裏面是各類類,在test類裏是對這些類的訪問。