夯實(shí)基礎(chǔ):Java的反射

前言

為什么要寫(xiě)Java的反射?因?yàn)楸救嗽陂喿x很多注入依賴(lài)這種開(kāi)源庫(kù)(類(lèi)似Dragger2,Butterknife)的源碼的時(shí)候,發(fā)現(xiàn)其代碼都運(yùn)用了大量的Java反射。本身Java反射就是框架的靈魂,為了能幫助更多的讀者讀懂這些開(kāi)源庫(kù)的代碼,我決定開(kāi)啟一個(gè)系列文章,分別是:Java的反射;Java的注解;利用Java的反射和注解手?jǐn)]一個(gè)Android注入依賴(lài)框架;ButterKnife源碼解析。這篇文章是該系列的第一篇:Java的反射。

什么是Java的反射機(jī)制

java允許開(kāi)發(fā)者在程序運(yùn)行過(guò)程中操作(訪問(wèn)和修改)類(lèi)的各種屬性以及方法。注意加粗的幾個(gè)字,“程序運(yùn)行過(guò)程中”,那什么是Java文件的程序運(yùn)行過(guò)程呢?

Java文件的程序運(yùn)行過(guò)程

首先Java文件的程序運(yùn)行過(guò)程分為三個(gè)階段:

  • Source(源代碼階段)
  • Class(類(lèi)對(duì)象階段)
  • Runtime(運(yùn)行時(shí))

Source(源代碼階段)

我們先創(chuàng)建一個(gè)Person.java文件,再用javac命令編譯Person.java文件,接著就會(huì)生成一個(gè)Person.class文件,此時(shí)這兩個(gè)文件都在磁盤(pán)里面,還有被加載到JVM內(nèi)存里面,這個(gè)時(shí)候就處于Source(源代碼階段)

java和class文件.png

Class(類(lèi)對(duì)象階段)

當(dāng)Person.class文件被類(lèi)加載器(ClassLoader)加載到JVM的內(nèi)存里,此時(shí)JVM運(yùn)行時(shí)的方法區(qū)里面會(huì)生成一個(gè)Class類(lèi)對(duì)象Class<Person> clz,這個(gè)Class類(lèi)對(duì)象非常重要,這里面包含了我們對(duì)類(lèi)的描述。比如說(shuō)我們現(xiàn)在這個(gè)Person.Java文件里有成員變量、構(gòu)造方法以及成員方法

public class Person extends Object {
    /**
     * 成員變量
     * */
    public String name;
    int age;
    protected int sex;
    private int id;
    /**
     * 構(gòu)造方法
     * */
    public Person(){

    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    /**
     * 成員方法
     * */
    private void eat(){
        System.out.println("eat-----");
    }
    private void drink(String drink) {
        System.out.println("drink----"+drink);
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                ", id=" + id +
                '}';
    }
}

那么Class類(lèi)對(duì)象是如何描述這個(gè)Person類(lèi)呢?Java的思想是一切皆對(duì)象,Class類(lèi)對(duì)象對(duì)類(lèi)的描述也是通過(guò)對(duì)象。

  • 成員變量:用的是Field對(duì)象
  • 構(gòu)造方法:用的是Constructor對(duì)象
  • 成員方法:用的是Method對(duì)象
    生成Class類(lèi)對(duì)象存在JVM內(nèi)存中的這個(gè)階段就是Class類(lèi)對(duì)象階段

Runtime(運(yùn)行時(shí))

這個(gè)階段可能大家相對(duì)就比較了解了,因?yàn)槲覀僯ava程序絕大多數(shù)的情況都處于這個(gè)階段,舉個(gè)例子,當(dāng)我們調(diào)用Person person = new Person();創(chuàng)建一個(gè)對(duì)象時(shí),接著會(huì)在堆內(nèi)存里生成一個(gè)person對(duì)象,但是這個(gè)person到底是如何被生成的呢?其實(shí)還是調(diào)用Class類(lèi)對(duì)象的方法,可見(jiàn)Class類(lèi)對(duì)象是多么重要的,而這個(gè)Class類(lèi)對(duì)象也是我們實(shí)現(xiàn)反射的基礎(chǔ)。

小結(jié)

這里做一個(gè)小結(jié),因?yàn)閖ava運(yùn)行過(guò)程中這三個(gè)階段對(duì)我們理解反射的概念非常重要,我這里畫(huà)了一個(gè)圖,大家理解一下


java程序運(yùn)行過(guò)程的三個(gè)階段.png

獲取Class類(lèi)對(duì)象

上面已經(jīng)講到了Class類(lèi)對(duì)象是我們反射的基礎(chǔ),有了Class類(lèi)對(duì)象才能實(shí)現(xiàn)反射,那該如何獲取Class類(lèi)對(duì)象呢?
java給我們提供了三種方式獲取Class類(lèi)對(duì)象,同時(shí)也對(duì)應(yīng)上面講的三個(gè)階段:

  • Source(源代碼階段):Class clz = Class.forName("com.example.kaka.Person");
    ,這個(gè)方法就是通過(guò)Java文件的全限定名(包名+類(lèi)名)把它的class文件加載到JVM內(nèi)存里面,此時(shí)我們就能得到Class類(lèi)文件,由于是源代碼階段,所以這個(gè)方法我們經(jīng)常用來(lái)加載配置文件,比如說(shuō)Spring在啟動(dòng)的時(shí)候會(huì)加載很多的配置文件,底層實(shí)現(xiàn)用的就是這個(gè)方法
  • Class類(lèi)對(duì)象:Class clz = Person.class;這個(gè)就直接用的Person的靜態(tài)屬性
  • Runtime(運(yùn)行時(shí)):因?yàn)槭沁\(yùn)行時(shí)了,首先我們得先有Person這個(gè)對(duì)象,Pserson p = new Person;Class clz = p.getClass();
    注意:通過(guò)以上三種方式生成的Class類(lèi)對(duì)象都是相同的,也就是在內(nèi)存的地址是一樣的,這里如果你明白類(lèi)的加載機(jī)制應(yīng)該很容易理解,不明白的可以溫習(xí)一下類(lèi)的加載機(jī)制。

Class類(lèi)對(duì)象能干什么

Class類(lèi)對(duì)象是對(duì)類(lèi)的描述,拿到了所有類(lèi)的信息,理論上我們就什么都能干,比如說(shuō):獲取類(lèi)的構(gòu)造方法、成員變量、成員方法、類(lèi)名等等...下面我們分別講一下

獲取類(lèi)的構(gòu)造方法

對(duì)類(lèi)的構(gòu)造方法修飾的對(duì)象是Constructor對(duì)象,Class類(lèi)對(duì)象里面提供了5個(gè)相關(guān)方法

  • public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
  • public Constructor<?>[] getConstructors() throws SecurityException
  • public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
    throws NoSuchMethodException, SecurityException
  • public Constructor<?>[] getDeclaredConstructors() throws SecurityException
  • public Constructor<?> getEnclosingConstructor()
    先聊前四個(gè)方法,前兩個(gè)方法的方法名沒(méi)有Declared修飾,他們只能獲取到用public修飾的構(gòu)造方法;后面兩個(gè)方法有有Declared修飾,他們可以獲取所有的構(gòu)造方法,不管是private還是protected修飾的,這里我們寫(xiě)一段代碼,打印一下log
    這里為了演示效果,我在Person類(lèi)里多加了幾個(gè)構(gòu)造方法
 /**
     * 構(gòu)造方法
     */
    public Person() {

    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private Person(int id) {
        this.id = id;
    }

    protected Person(int id, int sex) {
        this.id = id;
        this.sex = sex;
    }

打印log的代碼

        Class<Person> personClass = Person.class;
        System.out.println("-----------------所有public修飾的構(gòu)造方法-------------------");
        Constructor<?>[] constructors = personClass.getConstructors();
        for (Constructor constructor1:constructors) {
            System.out.println(constructor1);
        }
        System.out.println("---------------所有的構(gòu)造方法---------------------");
        Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();
        for (Constructor declaredConstructor:declaredConstructors) {
            System.out.println(declaredConstructor);
        }

看一下log的打印結(jié)果


運(yùn)行結(jié)果1.png

我們?cè)倏从袇?shù)的那兩個(gè)方法,他們是獲取指定方法名的構(gòu)造方法,接收的參數(shù)是參數(shù)類(lèi)型的Class類(lèi)對(duì)象;而無(wú)參的兩個(gè)方法后面帶了個(gè)s,也就是他們獲取的是構(gòu)造方法方法數(shù)組。

        Class<Person> personClass = Person.class;
        Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
        System.out.println("----------------指定參數(shù)類(lèi)型的public修飾的構(gòu)造方法-----------------");
        System.out.println(constructor);
        personClass.getDeclaredConstructor();

        constructor = personClass.getDeclaredConstructor(int.class);
        System.out.println("----------------指定參數(shù)類(lèi)型的構(gòu)造方法-----------------");
        System.out.println(constructor);
        personClass.getDeclaredConstructor();

運(yùn)行結(jié)果

運(yùn)行結(jié)果2.png

現(xiàn)在再講一下第5個(gè)方法,getEnclosingConstructor(),官方的解釋?zhuān)骸叭绻?Class 對(duì)象表示構(gòu)造方法中的一個(gè)本地或匿名類(lèi),則返回 Constructor 對(duì)象,它表示底層類(lèi)的立即封閉構(gòu)造方法。否則返回 null。簡(jiǎn)單來(lái)說(shuō),就是Person里面的構(gòu)造方法聲明了一個(gè)內(nèi)部類(lèi)InnerMan,此時(shí)用InnerMan的Class類(lèi)對(duì)象調(diào)用getEnclosingConstructor()獲取到的構(gòu)造方法是Person的構(gòu)造方法

 public Person() {
        class InnerMan {

        }
        InnerMan man = new InnerMan();
        Constructor<?> enclosingConstructor = man.getClass().getEnclosingConstructor();
        System.out.println(enclosingConstructor);
    }
運(yùn)行結(jié)果3.png

這個(gè)方法在實(shí)際應(yīng)用中比較少見(jiàn),大家知道即可

用過(guò)上面的方式我們拿到了類(lèi)的構(gòu)造方法對(duì)象Constructor,那我們現(xiàn)在要干嘛?反射機(jī)制的定義是Java允許開(kāi)發(fā)者在程序運(yùn)行過(guò)程中**操作(訪問(wèn)和修改)類(lèi)的各種屬性以及方法?,F(xiàn)在拿到Constructor,我們要做的操作就是創(chuàng)建對(duì)象。Constructor提供一個(gè)非常重要的api

  • public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
    調(diào)用這個(gè)方法我們就能得到一個(gè)對(duì)象,里面接收的是一個(gè)可變參數(shù),如果調(diào)用無(wú)參的構(gòu)造方法就什么都不傳,有參的就傳對(duì)應(yīng)的參數(shù)就行
        Class<Person> personClass = Person.class;
        Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
        Person person1 = constructor.newInstance("張三", 24);

        constructor = personClass.getConstructor();
        Person person2 = constructor.newInstance();

這樣我們就能創(chuàng)建Person對(duì)象了。我們?cè)僭囈幌聀rivate修飾的構(gòu)造方法,都知道private修飾的構(gòu)造方法在類(lèi)的外部是不允許被調(diào)用的,但是對(duì)反射而言不區(qū)分什么內(nèi)部外部,反射都可以得到,就是這么強(qiáng)大

        constructor = personClass.getDeclaredConstructor(int.class);
        constructor.setAccessible(true);
        Person person = constructor.newInstance(1);

注意:這里最重要的一句是constructor.setAccessible(true);,當(dāng)我們?cè)L問(wèn)private修飾的構(gòu)造方法、成員變量、成員方法,都需要調(diào)用這個(gè)API,它的意思是取消安全檢查機(jī)制,這樣我們就可以調(diào)用私有的構(gòu)造方法了,這一點(diǎn)非常重要,假如我們不加這一句代碼會(huì)怎么樣,我們?cè)囋?br>

運(yùn)行結(jié)果

直接就報(bào)錯(cuò)了,所以在訪問(wèn)私有屬性以及方法之前,必須要加上obj.setAccessible(true);

獲取類(lèi)的成員變量

之前講過(guò)對(duì)成員變量修飾的對(duì)象是Field,Class類(lèi)對(duì)象里面提供了四個(gè)相關(guān)的方法

  • public Field getField(String name) throws NoSuchFieldException
  • public Field[] getFields() throws SecurityException
  • public native Field getDeclaredField(String name) throws NoSuchFieldException
  • public native Field[] getDeclaredFields();
    跟前面的構(gòu)造方法類(lèi)似,首先看前兩個(gè)方法的方法名沒(méi)有Declared修飾,他們只能獲取到用public修飾的成員變量;后面兩個(gè)方法有有Declared修飾,他們可以獲取所有的成員變量,不管是private還是protected修飾的;我們?cè)倏从袇?shù)的那兩個(gè)方法,他們是獲取指定變量名的成員變量,而無(wú)參的兩個(gè)方法后面帶了個(gè)s,也就是他們獲取的是變量數(shù)組。
       Class<Person> personClass = Person.class;
        Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
        Person person1 = constructor.newInstance("張三", 24);
        Field[] declaredFields = personClass.getDeclaredFields();//獲取所有成員變量
        Field id = personClass.getDeclaredField("id");//獲取指定的成員變量
        Field name = personClass.getField("name");//獲取指定的public成員變量

現(xiàn)在我們拿到了成員變量了,對(duì)成員變量而言,我們的操作就是讀取和修改,F(xiàn)ield對(duì)象也提供了兩個(gè)重要的api

  • public native Object get(Object obj) throws IllegalArgumentException, IllegalAccessException;//獲取變量的值
  • public native void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException;//設(shè)置變量的值,第一個(gè)參數(shù)是對(duì)象,第二個(gè)參數(shù)是設(shè)置變量的值
        Class<Person> personClass = Person.class;
        Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
        Person person1 = constructor.newInstance("張三", 24);
//        Field[] declaredFields = personClass.getDeclaredFields();//獲取所有成員變量
//        Field id = personClass.getDeclaredField("id");
        Field name = personClass.getField("name");
        Object nameVal = name.get(person1);
        System.out.println(nameVal);
        name.set(person1,"李四");
        nameVal = name.get(person1);
        System.out.println(nameVal);

這里解釋一下,我們以name為例,先獲取一下name的值,然后在把name改成“李四”

運(yùn)行結(jié)果

控制臺(tái)打印的結(jié)果符合我們的預(yù)期,同樣地,當(dāng)我們?cè)L問(wèn)被private所修飾的成員變量也要調(diào)用obj.setAccessible(true);

        Field id = personClass.getDeclaredField("id");
        id.setAccessible(true);
        id.set(person1,3);
       //這里就不做演示了

獲取類(lèi)的成員方法

描述方法的對(duì)象是Method,Class類(lèi)對(duì)象給我們提供了5個(gè)獲取Method的相關(guān)方法

  • public Method[] getMethods() throws SecurityException:獲取所有public修飾的方法
  • public Method getMethod(String name, Class<?>... parameterTypes):獲取指定方法名以及參數(shù)類(lèi)型的被public修飾的成員方法
  • public Method[] getDeclaredMethods() throws SecurityException:獲取所有的成員方法
  • public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
    throws NoSuchMethodException, SecurityException:獲取指定方法名以及參數(shù)類(lèi)型的成員方法
  • public Method getEnclosingMethod():如果該 Class 對(duì)象表示成員方法中的一個(gè)本地或匿名類(lèi),則返回 Method 對(duì)象,它表示底層類(lèi)的立即封閉成員方法。否則返回 null?;旧细懊鎔etEnclosingConstructor()是類(lèi)似的。
        Method[] methods = personClass.getMethods();//獲取所有的public方法
        Method[] declaredMethods = personClass.getDeclaredMethods();//獲取所有的方法
        Method drink = personClass.getDeclaredMethod("drink", String.class);//獲取指定的方法名和參數(shù)類(lèi)型的方法

拿到了方法對(duì)象Method以后,我們還是要操作,只需要調(diào)用方法就行了,對(duì)應(yīng)的Method提供了一個(gè)重要的api

  • public native Object **invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException:第一個(gè)參數(shù)傳具體對(duì)象,第二個(gè)傳方法里的參數(shù),沒(méi)有就不傳
        Method drink = personClass.getDeclaredMethod("drink", String.class);
        drink.setAccessible(true);
        drink.invoke(person1,"可樂(lè)");

訪問(wèn)私有方法同樣要調(diào)用** drink.setAccessible(true);**

注意:關(guān)于Class對(duì)象還有其他一些比較常用的api,這里我們就不單獨(dú)羅列出來(lái)了,直接放到下面:

  • public String getName():獲取類(lèi)的全限定名
  • public Package getPackage():獲取Package對(duì)象
  • public Class<?>[] getInterfaces() :獲取接口數(shù)組
  • public Annotation[] getAnnotations():獲取注解
    至此,我們就通過(guò)反射的機(jī)制拿到了一個(gè)類(lèi)的所有信息,并可以操作其相關(guān)信息,建議大家自己寫(xiě)個(gè)小demo,跑一下

總結(jié)

反射機(jī)制可以允許我們?cè)趈ava程序運(yùn)行過(guò)程中操作類(lèi)的各種屬性。java程序運(yùn)行過(guò)程又分為三個(gè)階段,Source(源代碼);Class(類(lèi)對(duì)象);Runtime(運(yùn)行時(shí)),這三個(gè)階段又同時(shí)提供了獲取Class類(lèi)對(duì)象的方法,拿到了Class對(duì)象以后我們就可以操作類(lèi)的屬性(成員變量、構(gòu)造方法、成員方法),以此來(lái)實(shí)現(xiàn)了反射的效果。反射很強(qiáng)大,它允許我們?cè)谌魏蔚胤讲僮黝?lèi)的任何信息,因此在性能略微有影響,不過(guò)影響不大,因?yàn)橛布矫娴脑颍?0ms和70ms對(duì)我們來(lái)說(shuō)是幾乎沒(méi)有差距。反射也是Java框架的靈魂,掌握反射有助于我們理解使用的框架,更能幫助我們開(kāi)發(fā)自己的框架。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容