
感謝大家和我一起,在Android世界打怪升級!
反射在平時開發中使用幾率較小,但在各大框架中會頻繁使用(比如:老版本ButterKnife使用注解與反射初始化控制元件等,省略findViewById),如果有意向成為架構師,這塊知識的掌握必不可少,
一、反射是什么
平時開發中創建物件都是通過 new 關鍵字創建,通過該物件的實體,可以獲取該物件的可訪問成員變數或者呼叫可呼叫方法,此時我們明確知道使用的類是什么,
那如果我們不知道要初始化的類是什么,就需要使用到JAVA為我們提供的反射API了,
1.1 定義
反射可以在程式的運行時:
- 構造任意一個類的物件
- 了解任意一個物件所屬的類
- 了解任意一個類的成員變數和方法
- 呼叫任意一個物件的屬性和方法
這種動態獲取程式資訊以及動態呼叫物件的功能稱為反射機制,反射是JAVA被視為動態語言的關鍵,
1.2 原理
在運行時獲取到類,但是在運行時.java檔案已經在編譯階段被編譯成了.class檔案,所以反射的原理就是:運行時通過位元組碼檔案獲取到類的所有資訊,
1.3 優缺點
優點:
- 高自由度:可以無視訪問權限限制,被private修飾依然可以呼叫,
缺點:
- 性能差:反射特別耗時,慢于直接創建物件,所以在使用時要衡量帶來的收益是否大于性能的影響,
- 安全性差:反射的高自由度直接導致類的封裝性被破壞,
- 通過反射修改代碼時,由于是直接操作位元組碼檔案,如果對代碼不熟悉,及其容易因為修改而導致報錯,
- 反射中有時會直接使用方法名,那在后期維護期間,如果方法名修改被修改,也會產生報錯,
二、反射的使用
Class類中方法特別多,我們只以舉例的方式寫幾個常用的例子,大家只需記住通過反射可以獲取一個類中所有的成員變數和方法(無視權限),你想要的全都有,
2.1 運行時獲取類
從1.2章節反射的原理可以曉得,反射的使用需要先在運行時獲取到類,運行時獲取到類一共有四種方法,根據情況選擇:
1、運行時直接從類中獲取
Class<Fruit> fruitClass1 = Fruit.class;
2、運行時從物件中獲取對應的類
Fruit fruit = new Fruit();
Class fruitClass2 = fruit.getClass();
3、運行時通過Class類的靜態方法獲取
Class fruitClass3 = Class.forName("com.kproduce.androidstudy.test.Fruit");
4、通過類加載器獲取
Class fruitClass4 = getClassLoader().loadClass("com.kproduce.androidstudy.test.Fruit");
最終這四種方式獲取的Class都是相同的,
// 以下結果都是true
System.out.println(fruitClass1 == fruitClass2);
System.out.println(fruitClass2 == fruitClass3);
System.out.println(fruitClass3 == fruitClass4);
2.2 運行時創建物件
通過在2.1中獲取的Class類來創建物件,
// 在2.1中拿到的Class類
Class<Fruit> fruitClass = Fruit.class;
// 呼叫Class類中的方法創建物件
Fruit fruit = fruitClass.newInstance();
2.3 獲取構造方法
一個類的構造方法因為引數不同可以很多,所以有API可以直接獲取所有構造方法 或者 根據引數不同獲取某個構造方法,
// 帶有四個構造方法的類
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;
}
}
1、獲取所有構造方法:getConstructors()
Constructor<Fruit>[] constructors = (Constructor<Fruit>[]) fruitClass.getConstructors();
2、根據引數獲取單個構造方法:getConstructor(引數class…)
// 運行時拿到Class
Class fruitClass = Class.forName("com.kproduce.androidstudy.test.Fruit");
// 1、構造方法:Fruit()
fruitClass.getConstructor();
// 2、構造方法:Fruit(String)
fruitClass.getConstructor(String.class);
// 3、構造方法:Fruit(int)
fruitClass.getConstructor(int.class);
// 4、構造方法:Fruit(String, int)
fruitClass.getConstructor(String.class, int.class);
3、使用構造方法創建物件
// 構造方法:Fruit(String, int)
Constructor<Fruit> constructor = fruitClass.getConstructor(String.class, int.class);
// 根據構造方法 Fruit(String, int) 創建物件
Fruit apple = constructor.newInstance("蘋果", 1);
2.4 獲取類的所有方法
和獲取構造方法類似,有獲取所有方法和單個方法的API,但是有兩套供選擇,注意注釋的方法限制,
1、獲取所有方法:
- getMethods()
- getDeclaredMethods()
// 獲取所有方法,包含從父類繼承的,不包括private:
Method[] methods = fruitClass.getMethods();
// 獲取所有方法,不包含從父類繼承的,包括private:
Method[] declaredMethods = fruitClass.getDeclaredMethods();
2、根據引數獲取單個方法:
- getMethod(“方法名”, 引數class…)
- getDeclaredMethod(“方法名”,引數class…)
// 獲取單個方法,包含從父類繼承的,不包括private,可添加引數(引數多載)
fruitClass.getMethod("方法名", 引數class...);
// 獲取單個方法,不包含從父類繼承的,包括private,可添加引數(引數多載)
fruitClass.getDeclaredMethod("方法名", 引數class...);
3、使用方法
// 獲取方法
Method method = fruitClass.getDeclaredMethod("方法名", 引數class...);
// 如果方法是私有的需要加下面這句
method.setAccessible(true);
// 呼叫方法,引數是被呼叫的物件,方法的呼叫需要基于物件
method.invoke(constructor.newInstance("蘋果", 1));
2.5 獲取類的成員變量
和獲取方法基本一致,也有兩套,可以給變數賦值和取值,都是基于物件的,
1、獲取所有成員變數:
- getMethods()
- getDeclaredMethods()
// 獲取所有變數,包含從父類繼承的,不包括private:
Field[] fields = fruitClass.getFields();
// 獲取所有變數,不包含從父類繼承的,包括private:
Field[] declaredFields = fruitClass.getDeclaredFields();
2、根據名稱獲取單個成員變數:
- getField(@NonNull String name)
- getDeclaredField(@NonNull String var1)
// 獲取單個成員變數,包含從父類繼承的,不包括private
Field nameFiled = fruitClass.getField("name");
// 獲取單個成員變數,不包含從父類繼承的,包括private
Field typeFiled = fruitClass.getDeclaredField("type");
3、給成員變數賦值和獲取值
// 獲取值和賦值都是基于物件,所以先創建物件
Fruit apple = constructor.newInstance("蘋果", 1);
// 獲取name的成員變數
Field nameFiled = fruitClass.getField("name");
// 如果變數是私有的在操作之前需要加下面這句
nameFiled.setAccessible(true);
// 【取值】獲取name的值,值是“蘋果”
Object filed = nameFiled.get(apple);
// 【賦值】給apple物件,設定name的值為“香蕉”
nameFiled.set(apple, "香蕉");
總結
最后咱們再總結一下反射的知識點:
- 反射可以在程式的運行時,構造任意一個類的物件、了解任意一個物件所屬的類、了解任意一個類的成員變數和方法、呼叫任意一個物件的屬性和方法,
- 反射的原理是:運行時通過位元組碼檔案獲取到類的所有資訊,
- 反射的優點是自由度高,可以無視訪問權限限制,缺點是性能差、安全性差(破壞了類的封裝性),
- 反射需要先在運行時得到類,有四種方式,得到類之后可以了解其中的方法和成員變數,
- 反射中對方法的呼叫、成員變數的取值和賦值,都是基于物件進行操作,
這樣反射的介紹就結束了,希望大家讀完這篇文章,會對反射有一個更深入的了解,如果我的文章能給大家帶來一點點的福利,那在下就足夠開心了,
下次再見!

公眾號
下面是我的微信公眾號【老匡話Android】,所有的系列文章都會在公眾號同步更新,歡迎關注,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/281003.html
標籤:AI
上一篇:你已經用上 5G 網路了嗎?
