在談反射以前,先思考一個問題,java中如何建立一個對象,有哪幾種方式?
Java中建立對象大概有這幾種方式:java
一、使用new關鍵字:這是咱們最多見的也是最簡單的建立對象的方式數組
二、使用Clone的方法:不管什麼時候咱們調用一個對象的clone方法,JVM就會建立一個新的對象,將前面的對象的內容所有拷貝進去安全
三、使用反序列化:當咱們序列化和反序列化一個對象,JVM會給咱們建立一個單獨的對象mybatis
上邊是Java中常見的建立對象的三種方式,其實除了上邊的三種還有另一種方式,就是接下來咱們要討論的 「反射」框架
一、反射概述
1.1什麼是反射
在程序運行狀態中,對於任意一個類或對象,都可以獲取到這個類的全部屬性和方法(包括私有屬性和方法),這種動態獲取信息以及動態調用對象方法的功能就稱爲反射機制。簡單來說,經過反射,類對咱們是徹底透明的,想要獲取任何東西均可以。函數
反射它把Java類中的各個部分,映射成一個個的Java對象,拿到這些對象後能夠作一些事情。既然說反射是反射Java類中的各個組成部分,因此說咱們得知道一個類中有哪兒些部分?學習
例如,一個類有:成員變量,方法,構造方法,等信息,利用反射技術我們能夠把這些組成部分映射成一個個對象。測試
1.二、反射能幹什麼
通常來講反射是用來作框架的,或者說能夠作一些抽象度比較高的底層代碼,反射在平常的開發中用到的很少,可是咱們還必須搞懂它,由於搞懂了反射之後,能夠幫助我們理解框架的一些原理。因此說有一句很經典的話:反射是框架設計的靈魂。this
1.3 反射的優勢
- 能夠在程序運行過程當中,操做這些對象;
- 能夠解耦,提升程序的可擴展性。
在學習反射之前,咱們先來了解一下Java代碼在計算機中所經歷的三個階段:編碼
- Source源代碼階段:.java被編譯成*.class字節碼文件。
- Class類對象階段:.class字節碼文件被類加載器加載進內存,並將其封裝成Class對象(用於在內存中描述字節碼文件),Class對象將原字節碼文件中的成員變量抽取出來封裝成數組Field[],將原字節碼文件中的構造函數抽取出來封裝成數組Construction[],將成員方法封裝成數組Method[]。固然Class類內不止這三個,還封裝了不少,咱們經常使用的就這三個。
- RunTime運行時階段:使用new建立對象的過程。
1.4 獲取Class對象的三種方式
- 【Source源代碼階段】 Class.forName(「全類名」):將字節碼文件加載進內存,返回Class對象;
多用於配置文件,將類名定義在配置文件中,經過讀取配置文件加載類。 - 【Class類對象階段】 類名.class:經過類名的屬性class獲取;
多用於參數的傳遞 - 【Runtime運行時階段】對象.getClass():此方法是定義在Objec類中的方法,所以全部的類都會繼承此方法。
多用於對象獲取字節碼的方式
//方法演示 public class getClass { public static void main(String[] args) throws Exception { //方式一:Class.forName("全類名"); Class class1 = Class.forName("com.czxy.mybatis_plus.test.Person"); //Person自定義實體類 System.out.println("class1 = " + class1); //方式二:類名.class Class class2 = Person.class; System.out.println("class2 = " + class2); //方式三:對象.getClass(); Person person = new Person(); Class class3 = person.getClass(); System.out.println("class3 = " + class3); //比較三個對象 System.out.println(class1 == class2); //true System.out.println(class1 == class3); //true } }
運行結果:
經過上述比較三個對象的結果能夠得出一個結論:同一個字節碼文件(*.class)在一次程序運行過程當中,只會被加載一次,不管經過哪種方式獲取的Class對象都是同一個。
二、Class對象的功能
2.1 獲取功能
這裏只寫出一些經常使用的,具體能夠參看jdk的幫助文檔。
- 獲取成員變量
Field[] getFields() //獲取全部public修飾的成員變量 Field getField(String name) //獲取指定名稱的public修飾的成員變量 Field[] getDeclaredFields() //獲取全部的成員變量,不考慮修飾符 Field getDeclaredField(String name) //獲取指定的成員變量,不考慮修飾符
2. 獲取構造方法
Constructor<?>[] getConstructors() //獲取全部public修飾的構造函數 Constructor<T> getConstructor(類<?>... parameterTypes) //獲取指定的public修飾的構造函數 Constructor<?>[] getDeclaredConstructors() //獲取全部的構造函數,不考慮修飾符 Constructor<T> getDeclaredConstructor(類<?>... parameterTypes) //獲取指定的構造函數,不考慮修飾符
3. 獲取成員方法
Method[] getMethods() //獲取全部public修飾的成員方法 Method getMethod(String name, 類<?>... parameterTypes) //獲取指定名稱的public修飾的成員方法 Method[] getDeclaredMethods() //獲取全部的成員方法,不考慮修飾符 Method getDeclaredMethod(String name, 類<?>... parameterTypes) //獲取指定名稱的成員方法,不考慮修飾符
4. 獲取全類名
String getName()
2.2 Field:成員變量
- (1)設置值 set(Object obj, Object value)
- (2)獲取值 get(Object obj)
- (3)忽略訪問權限修飾符的安全檢查 setAccessible(true):暴力反射
2.3 測試的實體類
import lombok.Data; import lombok.ToString; @Data @ToString public class Person { public String a; //最大範圍public protected String b; //受保護類型 String c; //默認的訪問權限 private String d; //私有類型 }
2.4 測試getFields和getField(String name)方法
/** * 獲取成員變量 * * Field[] getFields() * * Field getField(String name) * @throws Exception */ public class reflectDemo{ public static void main(String[] args) throws Exception { //獲取Person的Class對象 Class personClass = Person.class; //一、Field[] getFields()獲取全部public修飾的成員變量 Field[] fields = personClass.getFields(); for(Field field : fields){ System.out.println(field); } System.out.println("============================="); //2.Field getField(String name) 獲取指定名稱的public修飾的成員變量 Field a = personClass.getField("a"); //獲取成員變量a的值 [也只能獲取公有的,獲取私有的或者不存在的字符會拋出異常] Person p = new Person(); Object value = a.get(p); System.out.println("value = " + value);//由於在Person類中a沒有賦值,因此爲null //設置成員變量a的屬性值 a.set(p,"李四"); System.out.println(p); } }
運行結果:
2.5 測試 getDeclaredFields 和 getDeclaredField(String name)方法
/** * Field[] getDeclaredFields() * Field getDeclaredField(String name) * @throws Exception */ public class reflectDemo1 { public static void main(String[] args) throws Exception { //獲取Person的Class對象 Class personClass = Person.class; //Field[] getDeclaredFields():獲取全部的成員變量,不考慮修飾符 Field[] declaredFields = personClass.getDeclaredFields(); for(Field filed : declaredFields){ System.out.println(filed); } System.out.println("==================================="); //Field getDeclaredField(String name) //獲取指定的成員變量,不考慮修飾符 Field d = personClass.getDeclaredField("d"); //private String d; Person p = new Person(); //Object value1 = d.get(p); //若是直接獲取會拋出異常,由於對於私有變量雖然能會獲取到,但不能直接set和get,必須忽略訪問權限修飾符的安全檢查後才能夠 //System.out.println("value1 = " + value1); //忽略訪問權限修飾符的安全檢查,又稱爲暴力反射 d.setAccessible(true); Object value2 = d.get(p); System.out.println("value2 = " + value2); } }
運行結果:
注意:若是沒有忽略訪問修飾符直接訪問會拋出以下所示的異常
三、 Constructor:構造方法
3.1修改測試的實體類
import lombok.Data; import lombok.ToString; @Data @ToString public class Person { private String name; private Integer age; //無參構造函數 public Person() { } //單個參數的構造函數,且爲私有構造方法 private Person(String name){ } //有參構造函數 public Person(String name, Integer age) { this.name = name; this.age = age; } }
3.2 測試獲取構造函數的方法
/** * 獲取構造方法 * Constructor<?>[] getConstructors() * Constructor<T> getConstructor(類<?>... parameterTypes) */ public class reflectDemo2{ public static void main(String[] args) throws Exception { //獲取Person的Class對象 Class personClass = Person.class; //Constructor<?>[] getConstructors() //獲取全部public修飾的構造函數 Constructor[] constructors = personClass.getConstructors(); for(Constructor constructor : constructors){ System.out.println(constructor); } System.out.println("=========================================="); //獲取無參構造函數 注意:Person類中必需要有無參的構造函數,否則拋出異常 Constructor constructor1 = personClass.getConstructor(); System.out.println("constructor1 = " + constructor1); //使用獲取到的無參構造函數建立對象 Object person1 = constructor1.newInstance(); System.out.println("person1 = " + person1); System.out.println("=========================================="); //獲取有參的構造函數 //public Person(String name, Integer age) 參數類型順序要與構造函數內一致,且參數類型爲字節碼文件類型 Constructor constructor2 = personClass.getConstructor(String.class,Integer.class); System.out.println("constructor2 = " + constructor2); //使用獲取到的有參構造函數建立對象 Object person2 = constructor2.newInstance("zhangsan", 22); //獲取的是有參的構造方法,就必需要指定參數 System.out.println(person2); System.out.println("========================================="); //對於通常的無參構造函數,咱們都不會先獲取無參構造器以後在進行初始化,而是直接調用Class類內的newInstance()方法 Object person3 = personClass.newInstance(); System.out.println("person3 = " + person3); } }
運行結果:
四、 Method:方法對象
4.1 執行方法:Object invoke(Object obj, Object… args)
import lombok.Data; import lombok.ToString; @Data @ToString public class Person { private String name; private Integer age; //無參構造函數 public Person() { } //有參構造函數 public Person(String name, Integer age) { this.name = name; this.age = age; } //無參方法 public void test(){ System.out.println("test..."); } //重載有參方法 public void test(String food){ System.out.println("test..."+food); } }
4.2 測試獲取成員方法的方法
/** * 獲取成員方法 * * Method[] getMethods() * * Method getMethod(String name, 類<?>... parameterTypes) */ public class reflectDemo3 { public static void main(String[] args) throws Exception { //獲取Person的Class對象 Class personClass = Person.class; //獲取指定名稱的方法 Method method1 = personClass.getMethod("test"); //執行方法 Person person = new Person(); Object rtValue = method1.invoke(person);//若是方法有返回值類型能夠獲取到,沒有就爲null //由於eat方法沒有返回值,故輸出null System.out.println("rtValue = " + rtValue); System.out.println("--------------------------------------------"); //獲取有參的函數,有兩個參數:第一個參數爲方法名,第二個參數是獲取方法的參數類型的字節碼文件 Method method2 = personClass.getMethod("test", String.class); //執行方法 method2.invoke(person,"嗨"); System.out.println("============================================"); //獲取方法列表 Method[] methods = personClass.getMethods(); for(Method method : methods){ //注意:獲取到的方法不只僅是Person類內本身的方法 System.out.println(method); //繼承Object中的方法也會被獲取到(固然前提是public修飾的) } } }
運行結果 :
咱們能夠看出Person內的public方法都被打印出來了,此外Object中的public方法也都被打印出來了。
同以前的敘述同樣,帶有Declared關鍵字的方法這兩個方法,能夠獲取到任意修飾符的方法。一樣也提供了setAccessible(true)方法進行暴力反射。
綜上所述:在反射面前沒有公有私有,均可以經過暴力反射解決。
五、獲取全類名
5.1 getName()方法獲取的類名是全類名(帶有路徑)
public class getNameDemo { public static void main(String[] args) throws Exception { //獲取Person的Class對象 Class personClass = Person.class; //獲取全類名 String className = personClass.getName(); System.out.println(className); } }
運行結果:
六、反射機制的應用案例
6.1 需求
寫一個"框架",在不改變該類的任何代碼的前提下,能夠幫咱們建立任意類的對象,而且執行其中的任意方法。
6.2 實現
(1)配置文件
(2)反射機制
6.3 步驟
(1)將須要建立的對象的全類名和須要執行的方法定義在配置文件中
(2)在程序中加載讀取配置文件
(3)使用反射技術把類文件加載進內存
(4)建立對象
(5)執行方法
6.4 代碼實現 須要的實體類
(1)Person類
public class Person { //無參方法 public void test(){ System.out.println("test..."); } }
(2)Student類
public class Student { //無參方法 public void study(){ System.out.println("I'm Student"); } }
6.5 編寫配置文件
className = com.czxy.mybatis_plus.test.Person methodName = test
6.6 實現框架
/** * 前提:不能改變該類的任何代碼。能夠建立任意類的對象,能夠執行任意方法 * 即:拒絕硬編碼 */ public class ReflectTest { public static void main(String[] args) throws Exception { //1.加載配置文件 //1.1建立Properties對象 Properties pro = new Properties(); //1.2加載配置文件 //1.2.1獲取class目錄下的配置文件(使用類加載器) ClassLoader classLoader = ReflectTest.class.getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream("pro.properties"); pro.load(inputStream); //2.獲取配置文件中定義的數據 String className = pro.getProperty("className"); String methodName = pro.getProperty("methodName"); //3.加載該類進內存 Class cls = Class.forName(className); //4.建立對象 Object obj = cls.newInstance(); //5.獲取方法對象 Method method = cls.getMethod(methodName); //6.執行方法 method.invoke(obj); } }
運行結果:
6.7 修改配置文件,再次運行
className = com.czxy.mybatis_plus.test.Student methodName = study
運行結果:
總結:
- 對於框架來講,已經對基礎的代碼進行了封裝並提供相應的API。
- 在框架的基礎上進行軟件開發,能夠簡化編碼。但若是咱們使用傳統的new形式來實例化,那麼當類更改時咱們就要修改Java代碼,這是很繁瑣的。
- 修改Java代碼之後咱們還要進行從新編譯、測試、發佈等一系列的操做。
- 而若是咱們僅僅只是修改配置文件,而不須要修改Java代碼就簡單的多。
- 此外使用反射還能達到解耦的效果,若是咱們使用的是new這種形式進行對象的實例化。
- 此時若是在項目的某一個小模塊中咱們的一個實例類丟失了,那麼在編譯期間就會報錯,會致使整個項目沒法啓動。
- 而對於反射建立對象Class.forName(「全類名」)這種形式,咱們在編譯期須要的僅僅只是一個字符串(全類名),在編譯期不會報錯,這樣其餘的模塊就能夠正常的運行,而不會由於一個模塊的問題致使整個項目崩潰。