詳解Java反射各類應用

Java除了給咱們提供在編譯期獲得類的各類信息以外,還經過反射讓咱們能夠在運行期間獲得類的各類信息。經過反射獲取類的信息,獲得類的信息以後,就能夠獲取如下相關內容:java

  • Class對象
  • 構造器
  • 變量
  • 方法
  • 私有變量與私有方法
  • 註解
  • 泛型
  • 數組

本文也將從上面幾個方面來介紹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......");
    }
}

Class對象

咱們應用會用到反射這個知識點,確定是想要在運行時獲得類的信息,根據類的那些信息去作一些特定的操做。那麼,首先無疑就是獲得類的信息,在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變量與方法。即經過getDeclaredFieldsgetDeclaredMethods方法獲得私有的變量與方法,一樣也支持用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

步驟有點繁瑣,下面一步步解釋:

  1. 反射獲得返回類型爲泛型類的方法
  2. 調用getGenericReturnType獲得方法返回類型中的參數化類型
  3. 判斷該type對象能不能向下轉型爲ParameterizedType
  4. 轉型成功,調用getActualTypeArguments獲得參數化類型的數組,由於有的泛型類,不僅只有一個參數化類型如Map<K,V>
  5. 取出數組中的每個的值,轉型爲Class對象輸出。

看結果確實獲得了泛型的具體的信息。
若是沒有一個方法返回泛型類型,那麼咱們也能夠經過方法的參數爲泛型類,來獲得泛型的參數化類型,如上面類中的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類裏是對這些類的訪問。

相關文章
相關標籤/搜索