不論是 Java 開發(fā) 還是 Android 開發(fā)偎捎,反射啤咽、泛型、注解 都是架構(gòu)設(shè)計(jì)中很重要的一個(gè)知識(shí)點(diǎn)廓奕。
為了更好的理解反射抱婉,需要先簡(jiǎn)單了解一些類加載器相關(guān)的知識(shí)。
類加載器
一懂从、類的初始化
當(dāng)程序要使用某個(gè)類時(shí)授段,如果該類還未被加載到內(nèi)存中,則系統(tǒng)會(huì)通過(guò)加載番甩,連接侵贵,初始化三步來(lái)實(shí)現(xiàn)對(duì)這個(gè)類進(jìn)行初始化。
- 加載
就是指將 class 文件讀入內(nèi)存缘薛,并為之創(chuàng)建一個(gè) Class 對(duì)象窍育,任何類被使用時(shí)系統(tǒng)都會(huì)建立一個(gè) Class 對(duì)象。 - 連接
- 驗(yàn)證:是否有正確的內(nèi)部結(jié)構(gòu)宴胧,并和其他類協(xié)調(diào)一致漱抓。
- 準(zhǔn)備:負(fù)責(zé)為類的靜態(tài)成員分配內(nèi)存,并設(shè)置默認(rèn)初始化值恕齐,靜態(tài)隨著類的加載而加載乞娄。
- 解析:將類的二進(jìn)制數(shù)據(jù)中的符號(hào)引用替換為直接引用。
- 初始化
為堆棧開辟內(nèi)存,默認(rèn)初始化仪或,構(gòu)造初始化确镊,等等。
二范删、類初始化時(shí)機(jī)
- 創(chuàng)建類的實(shí)例蕾域。
- 訪問(wèn)類的靜態(tài)變量,或者為靜態(tài)變量賦值到旦。
- 調(diào)用類的靜態(tài)方法旨巷。
- 使用反射方式來(lái)強(qiáng)制創(chuàng)建某個(gè)類或接口對(duì)應(yīng)的 java.lang.Class 對(duì)象。
- 初始化某個(gè)類的子類添忘。
- 直接使用 java.exe 命令來(lái)運(yùn)行某個(gè)主類采呐。
三、類加載器
負(fù)責(zé)將 .class 文件加載到內(nèi)在中昔汉,并為之生成對(duì)應(yīng)的 Class 對(duì)象懈万。
Bootstrap ClassLoader
根類加載器,也被稱為引導(dǎo)類加載器靶病,負(fù)責(zé) Java 核心類的加載会通,比如 System、String 等娄周。在 JDK 中 JRE 的 lib 目錄下 rt.jar 文件中涕侈。Extension ClassLoader
擴(kuò)展類加載器,負(fù)責(zé) JRE 的擴(kuò)展目錄中 jar 包的加載煤辨,在 JDK 中 JRE 的 lib 目錄下 ext 目錄裳涛。System ClassLoader
系統(tǒng)類加載器,負(fù)責(zé)在 JVM 啟動(dòng)時(shí)加載來(lái)自 java 命令的 class 文件众辨,以及 classpath 環(huán)境變量所指定的 jar 包和類路徑端三。也就是說(shuō),平時(shí)我們寫的 java 文件鹃彻,編譯后生成的 class 文件郊闯,都是通過(guò)該加載器進(jìn)行加載的。
通過(guò)這些描述我們就可以知道我們常用的東西的加載都是由誰(shuí)來(lái)完成的蛛株。
那么团赁,我們?nèi)绾问褂眠@些class文件中的內(nèi)容呢?這就是反射要研究的內(nèi)容谨履。
反射
Java 反射機(jī)制是在運(yùn)行狀態(tài)中欢摄,對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法笋粟;對(duì)于任意一個(gè)對(duì)象怀挠,都能夠調(diào)用它的任意一個(gè)方法和屬性析蝴;這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能成為 Java 語(yǔ)言的反射機(jī)制。
一绿淋、獲取 Class 類對(duì)象
要想解剖一個(gè)類嫌变,必須先要獲取到該類的字節(jié)碼文件對(duì)象。而解剖使用的就是 Class 類中的方法躬它。所以先要獲取到每一個(gè)字節(jié)碼文件對(duì)應(yīng)的 Class 類型的對(duì)象。
有三種方式獲取东涡,下面用這個(gè) Book.java 舉例:
public class Book {
private String name;
public int price;
public Book() {
}
Book(String name) {
this.name = name;
}
public Book(String name, int price) {
this.name = name;
this.price = price;
}
public void show() {
System.out.println("show");
}
public void function(String s) {
System.out.println("function: " + s);
}
public String returnValue(String name, int price) {
return name + " - " + price;
}
private void hello() {
System.out.println("hello");
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
-
Object 類的
getClass()
方法
在可以獲取到該實(shí)例對(duì)象的情況下冯吓,采用該方法。// 方式一 Book book = new Book(); Class c1 = book.getClass();
-
數(shù)據(jù)類型的靜態(tài)
class
屬性
在可以導(dǎo)入該類的情況下疮跑,采用該方法组贺。// 方式二 Class c2 = Book.class;
-
通過(guò)Class類的靜態(tài)方法
forName(String className)
在得知完整類名的情況下,采用該方法祖娘。// 方式三 try { Class c3 = Class.forName("com.ff.reflect.Book"); } catch (ClassNotFoundException e) { e.printStackTrace(); }
開發(fā)中經(jīng)常會(huì)使用方式三失尖,首先,方式三可以結(jié)合配置文件使用渐苏,從配置文件中獲取完整類名掀潮;其次,很多情況下不能獲取實(shí)例對(duì)象和導(dǎo)入類琼富。
二仪吧、獲取構(gòu)造方法
前提條件就是先要獲取到 Class 文件對(duì)象
Class clazz = null;
try {
clazz = Class.forName("com.ff.reflect.Book");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if (clazz == null) {
return;
}
獲取全部構(gòu)造方法
-
獲取所有公共構(gòu)造方法
getConstructors()
可以獲取到 public 修飾的構(gòu)造方法。Constructor[] constructors = clazz.getConstructors(); for (Constructor constructor : constructors) { System.out.println(constructor); }
打印結(jié)果:
public com.ff.reflect.Book(java.lang.String,int) public com.ff.reflect.Book()
-
獲取所有構(gòu)造方法
getDeclaredConstructors()
可以獲取到全部構(gòu)造方法鞠眉,包括 public薯鼠、protected、private 以及默認(rèn)修飾的構(gòu)造方法械蹋。Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); for (Constructor declaredConstructor : declaredConstructors) { System.out.println(declaredConstructor); }
打印結(jié)果:
private com.ff.reflect.Book(int) com.ff.reflect.Book(java.lang.String) public com.ff.reflect.Book(java.lang.String,int) public com.ff.reflect.Book()
獲取單個(gè)構(gòu)造方法
開發(fā)中我們一般需要使用一種構(gòu)造方法出皇,所以下面方式更為常用。
-
獲取單個(gè)公共構(gòu)造方法
getConstructor()
- 無(wú)參構(gòu)造
try { Constructor constructor = clazz.getConstructor(); Object object = constructor.newInstance();// 無(wú)參構(gòu)造 System.out.println(object); } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { e.printStackTrace(); }
- 帶參構(gòu)造
try { Constructor constructor = clazz.getConstructor(String.class, int.class); Object object = constructor.newInstance("Java", 18);// 兩個(gè)參數(shù)的構(gòu)造 System.out.println(object); } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); }
-
獲取單個(gè)非公共構(gòu)造方法
getDeclaredConstructor()
和上面獲取公共構(gòu)造是類似的哗戈,只不過(guò)將getConstructor()
替換為getDeclaredConstructor()
就可以獲取非 public 的構(gòu)造方法了郊艘。try { Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class); Object object = declaredConstructor.newInstance("Java"); System.out.println(object); } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); }
需要注意的是,如果反射得到的是私有構(gòu)造方法谱醇,那么直接調(diào)用會(huì)報(bào): IllegalAccessException 非法訪問(wèn)異常暇仲,需要使用暴力訪問(wèn),即設(shè)置
setAccessible(true)
副渴。try { Constructor constructor = clazz.getDeclaredConstructor(int.class); constructor.setAccessible(true);// 暴力訪問(wèn) Object object = constructor.newInstance(18);// 私有構(gòu)造 System.out.println(object); } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); }
三奈附、獲取成員變量
與上面獲取構(gòu)造方法大同小異
獲取全部成員變量
- 獲取所有公共成員變量
getFields()
Field[] fields = clazz.getFields(); for (Field field : fields) { System.out.println(field); }
- 獲取所有成員變量
getDeclaredFields()
Field[] declaredFields = clazz.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println(declaredField); }
獲取單個(gè)成員變量
-
獲取單個(gè)公共成員變量
getField()
try { Constructor constructor = clazz.getConstructor(); Object object = constructor.newInstance(); Field price = clazz.getField("price");// 獲取 price 成員變量 price.set(object, 18);// 修改成員變量的值 System.out.println(object); } catch (NoSuchFieldException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); }
-
獲取單個(gè)非公共成員變量
getDeclaredField()
try { Constructor constructor = clazz.getConstructor(); Object object = constructor.newInstance(); Field name = clazz.getDeclaredField("name");// 獲取 name 成員變量 name.set(object, "Java");// 修改成員變量的值 System.out.println(object); } catch (NoSuchFieldException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); }
需要注意的是,如果反射得到的是私有成員變量煮剧,那么直接調(diào)用會(huì)報(bào): IllegalAccessException 非法訪問(wèn)異常斥滤,需要使用暴力訪問(wèn)将鸵,即設(shè)置
setAccessible(true)
。try { Constructor constructor = clazz.getConstructor(); Object object = constructor.newInstance(); Field name = clazz.getDeclaredField("name");// 獲取 name 成員變量 name.setAccessible(true);// 暴力訪問(wèn)佑颇,可訪問(wèn)私有成員變量 name.set(object, "Java");// 修改成員變量的值 System.out.println(object); } catch (NoSuchFieldException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); }
四顶掉、獲取成員方法
獲取全部成員方法
-
獲取所有公共成員方法,包括父類
getMethods()
Method[] methods = clazz.getMethods(); for (Method method : methods) { System.out.println(method); }
-
獲取所有成員方法挑胸,不包含父類
getDeclaredMethods()
Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { System.out.println(declaredMethod); }
獲取單個(gè)成員方法
-
獲取單個(gè)公共成員方法
getMethod()
- 無(wú)參數(shù)痒筒、無(wú)返回值
try { Constructor constructor = clazz.getConstructor(); Object object = constructor.newInstance(); Method show = clazz.getMethod("show");// show 方法 show.invoke(object);// 調(diào)用 show 方法 } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); }
- 帶參數(shù)、無(wú)返回值
try { Constructor constructor = clazz.getConstructor(); Object object = constructor.newInstance(); Method function = clazz.getMethod("function", String.class);// function 方法 function.invoke(object, "hello");// 調(diào)用 function 方法茬贵,傳參 hello } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); }
- 帶多個(gè)參數(shù)簿透,有返回值
try { Constructor constructor = clazz.getConstructor(); Object object = constructor.newInstance(); Method returnValue = clazz.getMethod("returnValue", String.class, int.class);// returnValue 方法 Object string = returnValue.invoke(object, "Java", 18);// 調(diào)用 returnValue 方法,傳參解藻,得到方法返回值 System.out.println(string);// 打印 returnValue 方法返回值 } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); }
- 獲取單個(gè)非公共成員方法
getDeclaredMethod()
需要注意的是老充,如果反射得到的是私有成員方法,那么直接調(diào)用會(huì)報(bào): IllegalAccessException 非法訪問(wèn)異常螟左,需要使用暴力訪問(wèn)啡浊,即設(shè)置setAccessible(true)
。try { Constructor constructor = clazz.getConstructor(); Object object = constructor.newInstance(); Method hello = clazz.getDeclaredMethod("hello");// 私有成員方法 hello.setAccessible(true);// 暴力訪問(wèn) hello.invoke(object);// 調(diào)用 hello 方法 } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); }
反射的應(yīng)用
一胶背、跳過(guò)泛型檢查
向 ArrayList<Integer> 中添加字符串?dāng)?shù)據(jù)巷嚣。
由于泛型只在編譯期間生效,而反射是在運(yùn)行期間調(diào)用钳吟,所以可以利用這兩點(diǎn)進(jìn)行實(shí)現(xiàn):
/**
* 向 ArrayList<Integer> 中添加字符串?dāng)?shù)據(jù)
*/
private static void test() {
ArrayList<Integer> array = new ArrayList<>();
Class<? extends ArrayList> aClass = array.getClass();
try {
Method add = aClass.getMethod("add", Object.class);
add.invoke(array, "hello");
add.invoke(array, "world");
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
System.out.println(array);
}
二涂籽、通用工具類
設(shè)置某個(gè)對(duì)象的某個(gè)屬性為指定值:
public void setProperty(Object obj, String propertyName, Object value){},
此方法可將obj對(duì)象中名為propertyName的屬性的值設(shè)置為value砸抛。
public class Utils {
/**
* 設(shè)置某個(gè)對(duì)象的某個(gè)屬性為指定值
*
* @param obj 對(duì)象
* @param propertyName 屬性
* @param value 值
*/
public static void setProperty(Object obj, String propertyName, Object value) {
Class<?> aClass = obj.getClass();
try {
Field declaredField = aClass.getDeclaredField(propertyName);
declaredField.setAccessible(true);
declaredField.set(obj, value);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
使用工具類:
private static void test() {
Book book = new Book();
Utils.setProperty(book, "name", "Java");
Utils.setProperty(book, "price", 18);
System.out.println(book);
}
在架構(gòu)設(shè)計(jì)中的應(yīng)用也很常見评雌,比如動(dòng)態(tài)代理等等,就不在這里展開了直焙。
至此景东,基本的 Java 反射機(jī)制都已經(jīng)介紹完了。