
感謝大家和我一起,在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):
- 反射可以在程序的運(yùn)行時(shí),構(gòu)造任意一個(gè)類的對(duì)象、了解任意一個(gè)對(duì)象所屬的類、了解任意一個(gè)類的成員變量和方法、調(diào)用任意一個(gè)對(duì)象的屬性和方法。
- 反射的原理是:運(yùn)行時(shí)通過(guò)字節(jié)碼文件獲取到類的所有信息。
- 反射的優(yōu)點(diǎn)是自由度高,可以無(wú)視訪問(wèn)權(quán)限限制。缺點(diǎn)是性能差、安全性差(破壞了類的封裝性)。
- 反射需要先在運(yùn)行時(shí)得到類,有四種方式,得到類之后可以了解其中的方法和成員變量。
- 反射中對(duì)方法的調(diào)用、成員變量的取值和賦值,都是基于對(duì)象進(jìn)行操作。
這樣反射的介紹就結(jié)束了,希望大家讀完這篇文章,會(huì)對(duì)反射有一個(gè)更深入的了解。如果我的文章能給大家?guī)?lái)一點(diǎn)點(diǎn)的福利,那在下就足夠開(kāi)心了。
下次再見(jiàn)!
