Java中反射機制的使用詳解
反射的基本介紹
Java的發(fā)射機制(Reflection)是指在運行時動態(tài)地獲取類的信息并操作對象的能力。
Java的發(fā)射機制允許程序在運行時檢查和操作任意一個類、方法、屬性等的信息,包括了類名、方法名、屬性名、參數(shù)列表以及訪問修飾符等。
通過Java的發(fā)射機制,可以實現(xiàn)一些高級的功能,比如動態(tài)生成代理對象、動態(tài)生成類、動態(tài)配置對象等。同時,Java的一些框架也廣泛應用了發(fā)射機制,比如Spring框架中大量使用了反射機制來實現(xiàn)依賴注入和AOP等功能。
Java中一些常用的反射API包括Class、Method、Field、Constructor等。其中,Class類是反射機制的核心,通過它可以獲取一個類的所有信息。Method和Field則分別表示類中的方法和屬性,Constructor則表示類中的構(gòu)造方法。
Java的發(fā)射機制給Java編程帶來了更大的靈活性,但同時也需要注意到發(fā)射機制的運行效率相對較低,同時也可能破壞類的封裝性,因此在使用時需要謹慎考慮。
在了解反射的基本介紹之后,我們在來看看反射的需求,為什么會需要反射
先看一段代碼
代碼演示:
package com.reflection.question; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Properties; /** * 反射問題的引入 */ @SuppressWarnings({"all"}) public class ReflectionQuestion { public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { //根據(jù)配置文件 re.properties 指定信息, 創(chuàng)建Cat對象并調(diào)用方法hi //傳統(tǒng)的方式 new 對象 -》 調(diào)用方法 // Cat cat = new Cat(); // cat.hi(); ===> cat.cry() 修改源碼. //我們嘗試做一做 -> 明白反射 //1. 使用Properties 類, 可以讀寫配置文件 Properties的底層就是HashTable Properties properties = new Properties(); properties.load(new FileInputStream("src\\re.properties"));//加載配置文件的鍵值對到properties對象 String classfullpath = properties.get("classfullpath").toString();//"com.hspedu.Cat" 根據(jù)鍵獲取值 String methodName = properties.get("method").toString();//"hi" System.out.println("classfullpath=" + classfullpath); System.out.println("method=" + methodName); //2. 創(chuàng)建對象 , 傳統(tǒng)的方法,行不通 =》 反射機制 //new classfullpath(); //3. 使用反射機制解決 //(1) 加載類, 返回Class類型的對象cls Class cls = Class.forName(classfullpath); //(2) 通過 cls 得到你加載的類 com.hspedu.Cat 的對象實例 Object o = cls.newInstance(); System.out.println("o的運行類型=" + o.getClass()); //運行類型 //(3) 通過 cls 得到你加載的類 com.hspedu.Cat 的 methodName"hi" 的方法對象 // 即:在反射中,可以把方法視為對象(萬物皆對象) Method method1 = cls.getMethod(methodName); //(4) 通過method1 調(diào)用方法: 即通過方法對象來實現(xiàn)調(diào)用方法 System.out.println("============================="); method1.invoke(o); //傳統(tǒng)方法 對象.方法() , 反射機制 方法.invoke(對象) } }
反射的需求
Java反射機制的需求主要有以下幾個方面:
- 動態(tài)加載類和調(diào)用類方法:通過反射機制,可以在運行時動態(tài)的加載一個類,并調(diào)用該類的方法。這種方式可以實現(xiàn)更加靈活的代碼編寫,避免了在編譯期就要確定所有的類和方法的限制。
- 實現(xiàn)通用框架:通過反射機制,可以實現(xiàn)通用的框架,比如ORM(對象-關系映射)框架、依賴注入框架、AOP(面向切面編程)框架等。這些框架需要在運行時動態(tài)的獲取類的信息,然后進行相應的操作。
- 修改類的屬性和方法:通過反射機制,可以在運行時修改類的屬性和方法,從而實現(xiàn)更加靈活的代碼編寫。
- 實現(xiàn)序列化和反序列化:Java中的序列化和反序列化需要用到反射機制,通過反射機制可以獲取對象的屬性和方法,然后將其序列化為字節(jié)流或者反序列化為對象。
- 動態(tài)代理:通過反射機制,可以實現(xiàn)動態(tài)代理,即在運行時生成一個代理對象,通過代理對象來調(diào)用目標對象的方法,從而實現(xiàn)AOP等功能。
那么我們使用反射機制可以完成
1.在運行時判斷任意一個對象所屬的類
2.在運行時構(gòu)造任意一個類的對象
3.在運行時得到任意一個類所具有的成員變量和方法
4.在運行時調(diào)用任意一個對象的成員變量和方法
5.生成動態(tài)代理
反射相關的主要類
1.java.lang.Class:代表一個類,Class對象表示某個類加載后在堆中的對象
2.java.lang.reflect.Method: 代表類的方法, Method對象表示某個類的方法
3.java.lang.reflect.Field: 代表類的成員變量,Field對象表示某個類的成員變量
4.java.lang.reflect.Constructor: 代表類的構(gòu)造方法,Constructor對象表示構(gòu)造器
代碼演示:
注意:反射和我們正常的使用方式有些不同,我們正常調(diào)用方法時對象名.方法名,但是在反射中是 方法名.對象名這里要注意一下
package com.reflection; import java.io.FileInputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Properties; @SuppressWarnings({"all"}) public class Reflection01 { public static void main(String[] args) throws Exception { //1. 使用Properties 類, 可以讀寫配置文件 Properties properties = new Properties(); properties.load(new FileInputStream("src\\re.properties")); String classfullpath = properties.get("classfullpath").toString();//"com.hspedu.Cat" String methodName = properties.get("method").toString();//"hi" //2. 使用反射機制解決 //(1) 加載類, 返回Class類型的對象cls Class cls = Class.forName(classfullpath); //(2) 通過 cls 得到你加載的類 com.hspedu.Cat 的對象實例 Object o = cls.newInstance(); System.out.println("o的運行類型=" + o.getClass()); //運行類型 //(3) 通過 cls 得到你加載的類 com.hspedu.Cat 的 methodName"hi" 的方法對象 // 即:在反射中,可以把方法視為對象(萬物皆對象) Method method1 = cls.getMethod(methodName); //(4) 通過method1 調(diào)用方法: 即通過方法對象來實現(xiàn)調(diào)用方法 System.out.println("============================="); method1.invoke(o); //傳統(tǒng)方法 對象.方法() , 反射機制 方法.invoke(對象) //java.lang.reflect.Field: 代表類的成員變量, Field對象表示某個類的成員變量 //得到name字段 //getField不能得到私有的屬性 Field nameField = cls.getField("age"); // System.out.println(nameField.get(o)); // 傳統(tǒng)寫法 對象.成員變量 , 反射 : 成員變量對象.get(對象) //java.lang.reflect.Constructor: 代表類的構(gòu)造方法, Constructor對象表示構(gòu)造器 Constructor constructor = cls.getConstructor(); //()中可以指定構(gòu)造器參數(shù)類型, 返回無參構(gòu)造器 System.out.println(constructor);//Cat() Constructor constructor2 = cls.getConstructor(String.class); //這里傳入的 String.class 就是String類的Class對象 System.out.println(constructor2);//Cat(String name) } }
反射優(yōu)點和缺點
優(yōu)點:
- 動態(tài)性:Java反射機制可以在運行時動態(tài)地獲取類的信息和操作對象,使程序更加靈活和易于擴展。
- 通用性:可以通過反射機制操作任意類型的對象,無需知道對象的具體類型。
- 代碼復用性:反射機制可以讓代碼更加通用和復用,提高開發(fā)效率。
- AOP支持:反射機制是實現(xiàn)AOP(面向切面編程)的重要手段之一。
缺點:
- 性能較低:Java反射機制需要在運行時動態(tài)地獲取對象信息和方法,需要一定的時間開銷,因此性能較低。
- 安全問題:反射機制可以操作任意類型的對象,容易造成安全問題,需要謹慎使用,并在必要時進行安全檢查。
- 可讀性下降:由于反射機制支持動態(tài)的獲取和操作對象,因此代碼的可讀性會下降一些,需要謹慎使用。
- 不利于編譯器優(yōu)化:由于反射機制需要在運行時動態(tài)獲取對象信息和方法,因此編譯器很難對代碼進行優(yōu)化。
代碼演示;
在下面的代碼中 ,我們分別測試了,使用正常的方式去調(diào)用方法,和使用反射的方式去調(diào)用方法,并且在執(zhí)行方法開始之前記錄了開始時間,在方法執(zhí)行結(jié)束之后,記錄了結(jié)束時間,最后兩個相減,就可以得到方法的執(zhí)行時間,我們就可以看出,使用正常的方式去調(diào)用方法,和使用反射的方式去調(diào)用方法,的區(qū)別。
package com.reflection; import com.Cat; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * 測試反射調(diào)用a的性能,和優(yōu)化方案 */ public class Reflection02 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //Field //Method //Constructor m1(); m2(); m3(); } //傳統(tǒng)方法來調(diào)用hi public static void m1() { Cat cat = new Cat(); long start = System.currentTimeMillis(); for (int i = 0; i < 90; i++) { cat.hi(); } long end = System.currentTimeMillis(); System.out.println("m1() 耗時=" + (end - start)); } //反射機制調(diào)用方法hi public static void m2() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { Class cls = Class.forName("com.Cat"); Object o = cls.newInstance(); Method hi = cls.getMethod("hi"); long start = System.currentTimeMillis(); for (int i = 0; i < 900000000; i++) { hi.invoke(o);//反射調(diào)用方法 } long end = System.currentTimeMillis(); System.out.println("m2() 耗時=" + (end - start)); } //反射調(diào)用優(yōu)化 + 關閉訪問檢查 public static void m3() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { Class cls = Class.forName("com.Cat"); Object o = cls.newInstance(); Method hi = cls.getMethod("hi"); hi.setAccessible(true);//在反射調(diào)用方法時,取消訪問檢查 long start = System.currentTimeMillis(); for (int i = 0; i < 900000000; i++) { hi.invoke(o);//反射調(diào)用方法 } long end = System.currentTimeMillis(); System.out.println("m3() 耗時=" + (end - start)); } }
反射調(diào)用優(yōu)化
Java中反射調(diào)用通常比直接調(diào)用方法緩慢,因為它需要進行額外的動態(tài)類型檢查和查找。為了優(yōu)化反射調(diào)用,可以采用以下幾種方法:
緩存Method對象:通過緩存Method對象,避免動態(tài)查找,提高調(diào)用效率。
使用方法句柄(MethodHandle):方法句柄是一種更高效的調(diào)用機制,它可以繞過反射調(diào)用的開銷。方法句柄比反射調(diào)用更快,特別是在熱代碼路徑上。
使用invokedynamic指令:為了提高反射調(diào)用的性能,Java 7引入了invokedynamic指令。它允許動態(tài)綁定方法調(diào)用,避免了反射調(diào)用的開銷。
使用字節(jié)碼生成庫:字節(jié)碼生成庫可以生成字節(jié)碼,從而避免了反射調(diào)用的開銷。比如,cglib是一個非常常用的字節(jié)碼生成庫。
到此這篇關于Java中反射機制的使用詳解的文章就介紹到這了,更多相關Java反射機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring實現(xiàn)動態(tài)數(shù)據(jù)源切換的方法總結(jié)
這篇文章主要為大家詳細介紹了一種Spring實現(xiàn)動態(tài)數(shù)據(jù)源切換的方法,文中的示例代碼講解詳細,具有一定的學習價值,感興趣的小伙伴可以跟隨小編一起了解一下2023-06-06SpringBoot中調(diào)用通用URL的實現(xiàn)
在 Spring Boot 應用程序中,有時候我們需要調(diào)用一些通用的 URL 接口,本文主要介紹了SpringBoot中調(diào)用通用URL的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-07-07