文章內容儘量的詳細,方便本身後續查閱。javascript
java反射是指,在運行狀態中,對於任意一個類,都能知道這個類的全部屬性及方法,對於任何一個對象,都能調用他的任何一個方法和屬性,這種動態獲取新的及動態調用對象的方法的功能叫作反射.java
Java中萬物皆爲對象,每一個類也是一個對象,每一個類的java文件在編譯的時候會產生同名的.class文件,這個.class文件包含了這個java類的元數據信息,包括成員變量,屬性,接口,方法等,生成.class文件的同時會產生其對應的Class對象,並放在.class文件的末尾。當咱們new一個新對象或者引用靜態成員變量時,Java虛擬機(JVM)中的類加載器子系統會將對應Class對象加載到JVM中,而後JVM再根據這個類型信息相關的Class對象建立咱們須要實例對象或者提供靜態變量的引用值,在JVM中都只有一個Class對象,即在內存中每一個類有且只有一個相對應的Class對象。android
在Android 中,處於安全考慮,google會對系統的某些方法使用@hide或者使用Private修飾,致使咱們沒辦法正常調用此類方法,可是有些場景咱們又須要使用這些方法,這個時候就能夠直接經過反射來調用修改。web
好比 Android 中 StorageManager
類中的 getVolumePaths()
方法,該方法爲隱藏方法,沒辦法正常調用,可是在實際使用中咱們也可能用上,若是你有系統權限,那你就能夠像 Android SD卡及U盤插拔狀態監聽及內容讀取 這樣隨心所欲,若是沒有權限,那你就能夠經過反射來實現了,Demo放在最後,認真看完,你也會:grin:api
在Android9.0之後,Google對反射也作了限制,Google對反射的方法作了劃分,並針對不一樣的等級的隱藏方法作了反射限制。安全
白名單:SDK
淺灰名單:仍能夠訪問的非 SDK 函數/字段。
深灰名單:
對於目標 SDK 低於 API 級別 28 的應用,容許使用深灰名單接口。
對於目標 SDK 爲 API 28 或更高級別的應用:行爲與黑名單相同
黑名單:受限,不管目標 SDK 如何。 平臺將表現爲彷佛接口並不存在。 例如,不管應用什麼時候嘗試使用接口,平臺都會引起 NoSuchMethodError/NoSuchFieldException,即便應用想要了解某個特殊類別的字段/函數名單,平臺也不會包含接口。app名單列表ide
咱們先新建一個Person
類和Man
類,須要注意一下他們的繼承關係及成員變量和方法的修飾符 (正常狀況下不會這麼寫,我方便後面方法說明,就刻意的給了不一樣的修飾符)函數
Person.java
:post
public class Person {
public int age;
private String name;
public Person() {
}
private Person(int age, String name) {
this.age = age;
this.name = name;
}
private Person(int age) {
this.age = age;
}
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;
}
}
複製代碼
Man.java
:
public class Man extends Person {
private String address;
public int phoneNum;
public Man() {
}
public Man(String address) {
this.address = address;
}
private Man(int phoneNum, String address) {
this.address = address;
this.phoneNum = phoneNum;
}
public String getAddress() {
return address;
}
private void setAddress(String address) {
this.address = address;
}
public int getPhoneNum() {
return phoneNum;
}
private void setPhoneNum(int phoneNum) {
this.phoneNum = phoneNum;
}
}
複製代碼
前面說到過,java虛擬機(JVM)會根據這個類型信息相關的Class對象建立咱們須要實例對象或者提供靜態變量的引用值,因此,咱們要用到反射,就首先要拿到這個類的Class對象,獲取Class對象有下面三種方法
Class manClass = Man.class;
複製代碼
該方法若是路徑找不到會拋出
ClassNotFoundException
異常
try {
// forName 中要傳入完整路徑
manClass1 = Class.forName("com.evan.reflection.Man");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
複製代碼
Man main = new Man();
Class<? extends Man> manClass2 = main.getClass();
複製代碼
經過打印,能夠看到三者獲取結果一致。
拿到其Class對象,能夠直接經過 newInstance()
方法,獲取到類的實例對象,並對其操做
Man man = (Man) manClass.newInstance();
man.setPhoneNum(123);
int phoneNum = man.getPhoneNum();
System.out.println("getPhoneNum=" + phoneNum);
複製代碼
結果:
你可能有疑惑,繞來繞去又繞回來了,幹嗎不直接new一個對象,非要繞一大圈,其實咱們這裏最主要的是拿到其Class對象,而後用Class對象去執行私有方法或設置私有變量。
先貼結論,能夠跟着後面的demo看結論,可能就會比較清晰。
構造方法只針對本類
方法 | 說明 |
---|---|
getConstructors() | 獲取當前類公有構造方法 |
getConstructor(Class… parameterTypes) | 獲取參數類型匹配的公有構造方法 |
getDeclaredConstructors() | 獲取當前類全部構造方法,包括私有。 |
getDeclaredConstructor(Class… parameterTypes) | 獲取全部參數類型匹配的構造方法(公有+私有) |
我這裏貼心的再貼一下咱們寫的Man
類的構造方法,能夠看到一個公有的無參和一個公有的單參構造方法,一個私有的雙參構造方法。
public Man() {
}
public Man(String address) {
this.address = address;
}
private Man(int phoneNum, String address) {
this.address = address;
this.phoneNum = phoneNum;
}
複製代碼
獲取當前類中全部用public
修飾的構造方法。
Constructor[] constructors = manClass.getConstructors();
for (Constructor c : constructors) {
System.out.println("getConstructors--" + c);
}
複製代碼
結果:
從打印的結果能夠看到,咱們拿到了一個無參的構造方法和一個String參數的構造方法,而這兩個構造方法恰好是用Public
修飾的。
拿到該類的全部構造方法,無論修飾符是啥。
Constructor[] declaredConstructors = manClass.getDeclaredConstructors();
for (Constructor c : declaredConstructors) {
System.out.println("getDeclaredConstructors--" + c);
}
複製代碼
結果:
不用說了吧,所有都拿到了!!
根據參數類型Class對象,只能獲取指定公有構造方法。
try {
// getConstructor(String.class) 傳入對應構造方法的參數類型Class對象
Constructor constructorPublic = manClass.getConstructor(String.class);
System.out.println("getConstructor(String.class)=" + constructorPublic);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
複製代碼
結果:
該方法只能獲取公有構造,若是咱們去獲取私有的構造方法就會就會拋
NoSuchMethodException
異常。
獲取指定參數Class對象的構造方法,無限制,獲取公有或者私有均可以。
try {
constructorPublicDeclared = manClass.getDeclaredConstructor(String.class);
Constructor constructorPrivateDeclared2 = manClass.getDeclaredConstructor(int.class, String.class);
System.out.println("getDeclaredConstructor(String.class)--------" + constructorPublicDeclared);
System.out.println("getDeclaredConstructor(int.class, String.class)--------" + constructorPrivateDeclared2);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
複製代碼
結果:
拿到了構造方法咱們直接調用 newInstance()
方法並傳入對應參數能夠拿到類對象。
try {
// 調用 newInstance 傳入對應參數,獲取Man實例並進行操做。
Man man = (Man) constructorPublicDeclared.newInstance("Test");
String address = man.getAddress();
System.out.println("newInstance address=" + address);
} catch (InvocationTargetException e) {
e.printStackTrace();
}
// 結果
newInstance address=Test
複製代碼
Method 類
方法 | 說明 |
---|---|
getMethods() | 獲取類自己及其父類全部公有方法 |
getMethod(String name, Class… parameterTypes) | 獲取類自己及其父類經過方法名及參數類型指定的公有方法 |
getDeclaredMethods() | 獲取類自己全部方法 |
getDeclaredMethod(String name, Class… parameterTypes) | 經過類自己經過方法名及參數類型獲取本類指定的方法,無限制 |
獲取當前類及其父類的Public
方法
Method[] methods = manClass.getMethods();
for (Method method : methods) {
System.out.println("getMethods--->" + method);
}
複製代碼
結果:
能夠看到獲取的全是Public修飾的方法,不光獲取了自身類,其父類Person類的公有方法同樣打印出來了,大家可能會說Object類是什麼鬼?Object類位於java.lang包中,是全部java類的父類。
獲取本類的全部方法。
Method[] declaredMethods = manClass.getDeclaredMethods();
for (Method declareMethod : declaredMethods) {
System.out.println("getDeclaredMethods--->" + declareMethod);
}
複製代碼
結果:
獲取本類及父類指定方法名和參數Class對象的方法,好比獲取其父類的setName方法和本身的getPhoneNum方法
public void setName(String name)
public int getPhoneNum()
try {
// 獲取Person父類SetName方法
Method setName = manClass.getMethod("setName", String.class);
// 獲取本身 getPhoneNum 方法
Method getPhoneNum = manClass.getMethod("getPhoneNum");
System.out.println("getMethod(String name, Class<?>... parameterTypes)--->" + setName);
System.out.println("getMethod(String name, Class<?>... parameterTypes)--->" + getPhoneNum);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
複製代碼
結果:
若是你不信邪,去獲取私有方法,會報錯 NoSuchMethodException
獲取本類指定方法名和參數Class對象的方法,無限制
try {
Method setAddress = manClass.getDeclaredMethod("setAddress", String.class);
Method getAddress = manClass.getDeclaredMethod("getAddress");
System.out.println("getDeclaredMethod--->" + setAddress);
System.out.println("getDeclaredMethod--->" + getAddress);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
複製代碼
結果:
拿到方法後,方法調用使用:
Object invoke(Object obj, Object... args)
複製代碼
就拿上面的例子
// 私有方法賦予權限
setAddress.setAccessible(true);
setAddress.invoke(manClass.newInstance(), "重慶市");
複製代碼
setAccessible 當咱們須要對非公有方法進行操做的時候,須要先調用此方法賦予權限,否則也會拋異常
Field 類
方法 | 說明 |
---|---|
getFields() | 獲取類自己及其父類全部公有成員變量 |
getField(String name) | 獲取類自己及其父類指定的公有成員變量 |
getDeclaredFields() | 獲取類自己全部成員變量(私有,公有,保護) |
getDeclaredField(String name) | 獲取類自己指定名字的成員變量 |
獲取本類及父類全部公有變量
Field[] fields = manClass.getFields();
for (Field field : fields) {
System.out.println("getFields--->" + field);
}
複製代碼
結果:
獲取本類全部成員變量
Field[] fields = manClass.getDeclaredFields();
for (Field field : fields) {
System.out.println("getDeclaredFields--->" + field);
}
複製代碼
結果:
獲取本類或者父類指定的成員變量
try {
Field age = manClass.getField("age");
Field phoneNum = manClass.getField("phoneNum");
System.out.println("getField--->" + age);
System.out.println("getField--->" + phoneNum);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
複製代碼
結果:
獲取本類指定的成員變量,無限制
try {
Field address = manClass.getDeclaredField("address");
Field phoneNum = manClass.getDeclaredField("phoneNum");
System.out.println("getField--->" + address);
System.out.println("getField--->" + phoneNum);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
複製代碼
結果:
// 私有成員變量要賦予權限
address.setAccessible(true);
address.set(manClass.newInstance(), "重慶市");
phoneNum.set(manClass.newInstance(), 023);
複製代碼
囉嗦這麼久,直接拿文章開頭說的那個方法動手,再看一下這個隱藏方法。
StorageManager.java ——> getVolumePaths
getVolumePaths()
: 返回所有存儲卡路徑, 包括已掛載的和未掛載的
雖然這個方法是 Public
修飾的,可是使用了@hide
註解,咱們直接使用 StorageManager
對象是找不到這個方法的,以下:
StorageManager sm = (StorageManager)
this.getSystemService(Context.STORAGE_SERVICE);
Class<? extends StorageManager> storageManagerClass = sm.getClass();
複製代碼
// getVolumePaths 是用public修飾,因此這裏getMethod和getDeclaredMethod均可以
// getVolumePaths 方法沒有參數,能夠不填
Method method = storageManagerClass.getMethod("getVolumePaths");
複製代碼
若是方法非public
修飾,還須要 使用 setAccessible(true)
賦予權限
String[] paths = (String[]) method.invoke(sm, null);
複製代碼
完整代碼:
/**
* 反射調用 StorageManager ——> getVolumePaths 方法
*/
public void getVolumePaths() {
StorageManager sm = (StorageManager)
this.getSystemService(Context.STORAGE_SERVICE);
Class<? extends StorageManager> storageManagerClass = sm.getClass();
try {
// 反射拿到getVolumePaths方法
Method method =
storageManagerClass.getDeclaredMethod("getVolumePaths");
String[] paths = (String[]) method.invoke(sm, null);
for (String path : paths) {
Log.d(TAG, "getVolumePaths: path=" + path);
}
} catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
Log.e(TAG, "getVolumePaths: " + e.getLocalizedMessage(), e);
}
}
複製代碼
結果:
其實咋一看,反射仍是很簡單,主要區分私有和公有,以及獲取的是本類的仍是本類加父類,固然,還有更多方法,好比你能夠經過獲取的方法,獲取它的修飾符,變量等等,方法相似,本文內容仍是比較淺,敲一遍基本就知道是咋回事了,看的話可能有點繞,建議本身動手敲一遍,還有其其它獲取反射中的知識點你也能夠去拓展一下,反射在Android中使用仍是挺廣,後續可能會說到熱修復知識點,其中也涉及到反射知識。