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