02、反射是什么?——《Android打怪升級(jí)之旅》

感謝大家和我一起,在Android世界打怪升級(jí)!

反射在平時(shí)開(kāi)發(fā)中使用幾率較小,但在各大框架中會(huì)頻繁使用(比如:老版本ButterKnife使用注解與反射初始化控件等,省略findViewById),如果有意向成為架構(gòu)師,這塊知識(shí)的掌握必不可少。

一、反射是什么

平時(shí)開(kāi)發(fā)中創(chuàng)建對(duì)象都是通過(guò) new 關(guān)鍵字創(chuàng)建,通過(guò)該對(duì)象的實(shí)例,可以獲取該對(duì)象的可訪問(wèn)成員變量或者調(diào)用可調(diào)用方法,此時(shí)我們明確知道使用的類是什么。

那如果我們不知道要初始化的類是什么,就需要使用到JAVA為我們提供的反射API了。

1.1 定義

反射可以在程序的運(yùn)行時(shí)

  • 構(gòu)造任意一個(gè)類的對(duì)象
  • 了解任意一個(gè)對(duì)象所屬的類
  • 了解任意一個(gè)類的成員變量和方法
  • 調(diào)用任意一個(gè)對(duì)象的屬性和方法

這種動(dòng)態(tài)獲取程序信息以及動(dòng)態(tài)調(diào)用對(duì)象的功能稱為反射機(jī)制。反射是JAVA被視為動(dòng)態(tài)語(yǔ)言的關(guān)鍵。

1.2 原理

在運(yùn)行時(shí)獲取到類,但是在運(yùn)行時(shí).java文件已經(jīng)在編譯階段被編譯成了.class文件,所以反射的原理就是:運(yùn)行時(shí)通過(guò)字節(jié)碼文件獲取到類的所有信息。

1.3 優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

  • 高自由度:可以無(wú)視訪問(wèn)權(quán)限限制,被private修飾依然可以調(diào)用。

缺點(diǎn):

  • 性能差:反射特別耗時(shí),慢于直接創(chuàng)建對(duì)象,所以在使用時(shí)要衡量帶來(lái)的收益是否大于性能的影響。
  • 安全性差:反射的高自由度直接導(dǎo)致類的封裝性被破壞。
    • 通過(guò)反射修改代碼時(shí),由于是直接操作字節(jié)碼文件,如果對(duì)代碼不熟悉,及其容易因?yàn)樾薷亩鴮?dǎo)致報(bào)錯(cuò)。
    • 反射中有時(shí)會(huì)直接使用方法名,那在后期維護(hù)期間,如果方法名修改被修改,也會(huì)產(chǎn)生報(bào)錯(cuò)。

二、反射的使用

Class類中方法特別多,我們只以舉例的方式寫(xiě)幾個(gè)常用的例子,大家只需記住通過(guò)反射可以獲取一個(gè)類中所有的成員變量和方法(無(wú)視權(quán)限),你想要的全都有

2.1 運(yùn)行時(shí)獲取類

從1.2章節(jié)反射的原理可以曉得,反射的使用需要先在運(yùn)行時(shí)獲取到類,運(yùn)行時(shí)獲取到類一共有四種方法,根據(jù)情況選擇:

  • 運(yùn)行時(shí)直接從類中獲取

    Class<Fruit> fruitClass1 = Fruit.class;
    
  • 運(yùn)行時(shí)從對(duì)象中獲取對(duì)應(yīng)的類

    Fruit fruit = new Fruit();
    Class fruitClass2 = fruit.getClass();
    
  • 運(yùn)行時(shí)通過(guò)Class類的靜態(tài)方法獲取

    Class fruitClass3 = Class.forName("com.kproduce.androidstudy.test.Fruit");
    
  • 通過(guò)類加載器獲取

    Class fruitClass4 = getClassLoader().loadClass("com.kproduce.androidstudy.test.Fruit");
    

最終這四種方式獲取的Class都是相同的。

// 以下結(jié)果都是true
System.out.println(fruitClass1 == fruitClass2);
System.out.println(fruitClass2 == fruitClass3);
System.out.println(fruitClass3 == fruitClass4);

2.2 運(yùn)行時(shí)創(chuàng)建對(duì)象

通過(guò)在2.1中獲取的Class類來(lái)創(chuàng)建對(duì)象。

// 在2.1中拿到的Class類
Class<Fruit> fruitClass = Fruit.class;

// 調(diào)用Class類中的方法創(chuàng)建對(duì)象
Fruit fruit = fruitClass.newInstance();

2.3 獲取構(gòu)造方法

一個(gè)類的構(gòu)造方法因?yàn)閰?shù)不同可以很多,所以有API可以直接獲取所有構(gòu)造方法 或者 根據(jù)參數(shù)不同獲取某個(gè)構(gòu)造方法

// 帶有四個(gè)構(gòu)造方法的類
public class Fruit {

    public String name;
    private int type;
    
    public Fruit() {
    }

    public Fruit(String name) {
        this.name = name;
    }

    public Fruit(int type) {
        this.type = type;
    }

    public Fruit(String name, int type) {
        this.name = name;
        this.type = type;
    }
}
  • 獲取所有構(gòu)造方法:getConstructors()

    Constructor<Fruit>[] constructors = (Constructor<Fruit>[]) fruitClass.getConstructors();
    
  • 根據(jù)參數(shù)獲取單個(gè)構(gòu)造方法:getConstructor(@Nullable Class<?>... parameterTypes)

    // 運(yùn)行時(shí)拿到Class
    Class fruitClass = Class.forName("com.kproduce.androidstudy.test.Fruit");
    
    // 1、構(gòu)造方法:Fruit()
    fruitClass.getConstructor();
    
    // 2、構(gòu)造方法:Fruit(String)
    fruitClass.getConstructor(String.class);
    
    // 3、構(gòu)造方法:Fruit(int)
    fruitClass.getConstructor(int.class);
    
    // 4、構(gòu)造方法:Fruit(String, int)
    fruitClass.getConstructor(String.class, int.class);
    
  • 使用構(gòu)造方法創(chuàng)建對(duì)象

    // 構(gòu)造方法:Fruit(String, int)
    Constructor<Fruit> constructor = fruitClass.getConstructor(String.class, int.class);
    
    // 根據(jù)構(gòu)造方法 Fruit(String, int) 創(chuàng)建對(duì)象
    Fruit apple = constructor.newInstance("蘋果", 1);
    

2.4 獲取類的所有方法

和獲取構(gòu)造方法類似,有獲取所有方法和單個(gè)方法的API,但是有兩套供選擇,注意注釋的方法限制。

  • 獲取所有方法:getMethods()、getDeclaredMethods()

    // 獲取所有方法,包含從父類繼承的,不包括private:
    Method[] methods = fruitClass.getMethods();
    
    // 獲取所有方法,不包含從父類繼承的,包括private:
    Method[] declaredMethods = fruitClass.getDeclaredMethods();
    
  • 根據(jù)參數(shù)獲取單個(gè)方法:getMethod("方法名", @Nullable Class<?>... parameterTypes)、getDeclaredMethod("方法名",@Nullable Class<?>... parameterTypes)

    // 獲取單個(gè)方法,包含從父類繼承的,不包括private,可添加參數(shù)(參數(shù)重載)
    fruitClass.getMethod("方法名", 參數(shù)class...);
    
    // 獲取單個(gè)方法,不包含從父類繼承的,包括private,可添加參數(shù)(參數(shù)重載)
    fruitClass.getDeclaredMethod("方法名", 參數(shù)class...);
    
  • 使用方法

    // 獲取方法
    Method method = fruitClass.getDeclaredMethod("方法名", 參數(shù)class...);
    
    // 如果方法是私有的需要加下面這句
    method.setAccessible(true);
    
    // 調(diào)用方法,參數(shù)是被調(diào)用的對(duì)象,方法的調(diào)用需要基于對(duì)象
    method.invoke(constructor.newInstance("蘋果", 1));
    

2.5 獲取類的成員變量

和獲取方法基本一致,也有兩套,可以給變量賦值和取值,都是基于對(duì)象的。

  • 獲取所有成員變量:getMethods()、getDeclaredMethods()

    // 獲取所有變量,包含從父類繼承的,不包括private:
    Field[] fields = fruitClass.getFields();
    
    // 獲取所有變量,不包含從父類繼承的,包括private:
    Field[] declaredFields = fruitClass.getDeclaredFields();
           
    
  • 根據(jù)名稱獲取單個(gè)成員變量:getField(@NonNull String name)、getDeclaredField(@NonNull String var1)

    // 獲取單個(gè)成員變量,包含從父類繼承的,不包括private
    Field nameFiled = fruitClass.getField("name");
    
    // 獲取單個(gè)成員變量,不包含從父類繼承的,包括private
    Field typeFiled = fruitClass.getDeclaredField("type");
    
  • 給成員變量賦值和獲取值

    // 獲取值和賦值都是基于對(duì)象,所以先創(chuàng)建對(duì)象
    Fruit apple = constructor.newInstance("蘋果", 1);
    
    // 獲取name的成員變量
    Field nameFiled = fruitClass.getField("name");
    
    // 如果變量是私有的在操作之前需要加下面這句
    nameFiled.setAccessible(true);
    
    // 【取值】獲取name的值,值是“蘋果”
    Object filed = nameFiled.get(apple);
    
    // 【賦值】給apple對(duì)象,設(shè)置name的值為“香蕉”
    nameFiled.set(apple, "香蕉");
    
    

總結(jié)

最后咱們?cè)倏偨Y(jié)一下反射的知識(shí)點(diǎn):

  1. 反射可以在程序的運(yùn)行時(shí),構(gòu)造任意一個(gè)類的對(duì)象、了解任意一個(gè)對(duì)象所屬的類、了解任意一個(gè)類的成員變量和方法、調(diào)用任意一個(gè)對(duì)象的屬性和方法。
  2. 反射的原理是:運(yùn)行時(shí)通過(guò)字節(jié)碼文件獲取到類的所有信息。
  3. 反射的優(yōu)點(diǎn)是自由度高,可以無(wú)視訪問(wèn)權(quán)限限制。缺點(diǎn)是性能差、安全性差(破壞了類的封裝性)。
  4. 反射需要先在運(yùn)行時(shí)得到類,有四種方式,得到類之后可以了解其中的方法和成員變量。
  5. 反射中對(duì)方法的調(diào)用、成員變量的取值和賦值,都是基于對(duì)象進(jìn)行操作。

這樣反射的介紹就結(jié)束了,希望大家讀完這篇文章,會(huì)對(duì)反射有一個(gè)更深入的了解。如果我的文章能給大家?guī)?lái)一點(diǎn)點(diǎn)的福利,那在下就足夠開(kāi)心了。

下次再見(jiàn)!


?著作權(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)容

  • 感謝大家和我一起,在Android世界打怪升級(jí)! 泛型,一個(gè)所有人都知道怎么用,在JAVA世界老生常談的特性。更需...
    老匡話Android閱讀 448評(píng)論 0 0
  • ## 引言 ### java中創(chuàng)建對(duì)象有幾種方式? #### 1.使用new關(guān)鍵字 #### 2.使用clone方...
    芋頭888閱讀 657評(píng)論 1 0
  • 反射總結(jié)慕課網(wǎng) 反射的視頻 什么是反射 反射是能夠讓java代碼訪問(wèn)一個(gè)已經(jīng)加載的類的字段,變量,方法和構(gòu)造器等信...
    付小影子閱讀 943評(píng)論 0 2
  • 學(xué)習(xí)Android的同學(xué)注意了?。?!學(xué)習(xí)過(guò)程中遇到什么問(wèn)題或者想獲取學(xué)習(xí)資源的話,歡迎加入Android學(xué)習(xí)交流群...
    kingZXY2009閱讀 356評(píng)論 0 0
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭,有人歡樂(lè)有人憂愁,有人驚喜有人失落,有的覺(jué)得收獲滿滿有...
    陌忘宇閱讀 8,898評(píng)論 28 54

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