Java是一門準動態語言,是因為存在反射機制,如果你不會是不是就等於白學了? 看完不會,請評論,我親自給你解釋,嘻嘻! ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200503024135960.png) #### 什麼是動態語言?   動態語言,是指程式在執行時可以改變其結構:新的函式可以被引進,已有的函式可以被刪除等在結構上的變化。比如JavaScript便是一個典型的動態語言。    除此之外如Ruby、Python、OC等也都屬於動態語言,而C、C++、Java等語言則不屬於動態語言。 動態型別語言,就是型別的檢查是在執行時做的,是不是合法的要到執行時才判斷,例如JavaScript就沒有編譯錯誤,只有執行錯誤。 **靜態語言**   而靜態型別語言的型別判斷是在執行前判斷(如編譯階段),比如java就是靜態型別語言,靜態型別語言為了達到多型會採取一些型別鑑別手段,如繼承、介面,而動態型別語言卻不需要, Java的反射機制被視為Java為準動態語言的主要的一個關鍵性質,這個機制允許程式在執行時透過反射取得任何一個已知名稱的class的內部資訊,包括: 正在執行中的類的屬性資訊,正在執行中的類的方法資訊,正在執行中的類的構造資訊,正在執行中的類的訪問修飾符,註解等等。 動態語言無時不刻在體現動態性,而靜態語言也在通過其他方法來趨近於去彌補靜態語言的缺陷。 #### **為什麼麼要使用反射:** 1. 反射是框架設計的靈魂 **框架:** 半成品軟體。可以在框架的基礎上進行軟體開發,簡化編碼。學習框架並不需要了解反射,但是要是想自己寫一個框架,那麼就要對反射機制有很深入的瞭解。 2. 解耦,提高程式的可擴充套件性 3. 在執行時判斷任意一個物件所屬的類。 4. 在執行時構造任意一個類的物件。 5. 在執行時判斷任意一個類所具有的成員變數和方法。 6. 在執行時呼叫任意一個物件的方法。 #### 什麼是反射: ##### **定義:** JAVA反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意方法和屬性;這種動態獲取資訊以及動態呼叫物件方法的功能稱為java語言的反射機制。 **簡單來說:** 將類的各個組成部分封裝成其他物件 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/2020050302422588.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzYyNzExOA==,size_16,color_FFFFFF,t_70) #### **反射機制的實現原理** ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200502165038564.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzYyNzExOA==,size_16,color_FFFFFF,t_70) **Java程式碼在計算機中經歷的三個階段** 1. Source原始碼階段:*.java被編譯成*.class位元組碼檔案。 2. Class類物件階段:*.class位元組碼檔案被類載入器載入進記憶體,並將其封裝成Class物件(用於在記憶體中描述位元組碼檔案),Class物件將原位元組碼檔案中的成員變數抽取出來封裝成陣列Field[],將原位元組碼檔案中的建構函式抽取出來封裝成陣列Construction[],在將成員方法封裝成Method[]。當然Class類內不止這三個,還封裝了很多,我們常用的就這三個。 3. RunTime執行時階段:建立物件的過程new。 ##### 獲取Class物件的方式: 1. **Class.forname("類全名"):** 將位元組碼載入進記憶體,返回Class物件。 **一般用於:** 配置檔案,將類名定義在配置檔案中,讀取檔案,載入類。 2. **類名.class:** 通過類名的屬性Class獲取 **一般用於:** 引數傳遞 3. **物件.getclass()獲取:** getclass()方法在Object類中定義 **一般用於:** 物件獲取位元組碼的方式 **補充:** 同一個位元組碼檔案(*.class)在一次程式執行中,只會被載入一次,不論通過哪一種方式獲取的Class物件都是同一個。 **舉例:** ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200503002603585.png) ```java public void Main() throws ClassNotFoundException { //方式一:Class.forName("全類名"); Class cls1 = Class.forName("com.test.domain.Person"); //Person自定義實體類 System.out.println("cls1 = " + cls1); //方式二:類名.class Class cls2 = Person.class; System.out.println("cls2 = " + cls2); //方式三:物件.getClass(); Person person = new Person(); Class cls3 = person.getClass(); System.out.println("cls3 = " + cls3); // == 比較三個物件 System.out.println("cls1 == cls2 : " + (cls1 == cls2)); //true System.out.println("cls1 == cls3 : " + (cls1 == cls3)); //true //結論:同一個位元組碼檔案(*.class)在一次程式執行過程中,只會被載入一次,無論通過哪一種方式獲取的Class物件都是同一個。 } ``` ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200503002618116.png) ##### Class物件功能: ###### **獲取功能:** **1 獲取成員變數們** ```java Field[] getFields() :獲取所有public修飾的成員變數 Field getField(String name) 獲取指定名稱的 public修飾的成員變數 Field[] getDeclaredFields() 獲取所有的成員變數,不考慮修飾符 Field getDeclaredField(String name) //需要忽略訪問許可權修飾符的安全檢查 setAccessible(true):暴力反射,不然會報錯 ``` > 具體測試看下文! **2.獲取構造方法們** ```java Constructor[] getConstructors() Constructor getConstructor(類... parameterTypes) Constructor[] getDeclaredConstructors() Constructor getDeclaredConstructor(類... parameterTypes) ``` > 具體測試看下文! > **3.獲取成員方法們:** ```java Method[] getMethods() Method getMethod(String name, 類... parameterTypes) Method[] getDeclaredMethods() Method getDeclaredMethod(String name, 類... parameterTypes) //需要忽略訪問許可權修飾符的安全檢查 setAccessible(true):暴力反射,不然會報錯 ``` > 具體測試看下文! > **4.獲取全類名** ```java String getName() ``` `getClass()`方法是Object類的方法,需要注意一點獲取的類名是全類名(帶有路徑) **舉例:** ```java package Test; public class Reflect { public static void main(String[] args) throws Exception { Class Tst = Test.class; String s=Tst.getName(); System.out.println(s); } } ``` **執行結果:** ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200503021855816.png) ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200503024319516.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzYyNzExOA==,size_16,color_FFFFFF,t_70) ###### Field:成員變數 1. 設定值 `void set(Object obj, Object value)` 2. 獲取值 `get(Object obj)` **舉例:** ```java package Test; public class Test { public String a; protected String b; private String c; String d; } package Test; import java.lang.reflect.Field; public class Reflect { public static void main(String[] args) throws Exception { Class Tst = Test.class; Test tst=new Test(); System.out.println("-------------------測試getField--------------------"); Field[] fields=Tst.getFields(); for(Field f:fields) { System.out.println(f); } Field a=Tst.getField("a"); a.set(tst, "我是設定值"); System.out.println(a.get(tst)); System.out.println("\n-------------測試getDeclaredField------------------"); Field[] fields2=Tst.getDeclaredFields(); for(Field f:fields2) { f.setAccessible(true);//不加出不來,詳情請看上文 System.out.println(f); } Field b=Tst.getDeclaredField("b"); b.set(tst, "我是私有的設定值"); System.out.println(b.get(tst)); } } ``` **測試結果:** ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200503013158287.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzYyNzExOA==,size_16,color_FFFFFF,t_70) ###### Constructor:構造方法 建立物件:T newInstance(Object... initargs) 注意:如果使用空引數構造方法建立物件,操作可以簡化:Class物件的newInstance方法 作用就是用它來建立物件 **舉例:** ```java package Test; public class Test { public String a; //構造方法 public Test( ) {} public Test(String a) { this.a = a; } } package Test; import java.lang.reflect.Constructor; import java.lang.reflect.Field; public class Reflect { public static void main(String[] args) throws Exception { Class Tst = Test.class; Constructor[] constructors = Tst.getConstructors(); for (Constructor constructor : constructors) { // Constructor 物件reflect包下的 import java.lang.reflect.Constructor; System.out.println(constructor); } System.out.println("------------------無參建構函式建立物件----------------------"); Constructor cst=Tst.getConstructor(); //獲得無參建構函式 System.out.println(cst); Object test=cst.newInstance(); //利用無參建構函式建立物件 System.out.println(test); System.out.println("------------------有參建構函式建立物件----------------------"); Constructor cst2=Tst.getConstructor(String.class); //獲得有參建構函式 System.out.println(cst2); Object test2=cst2.newInstance("張3");//利用有參建構函式建立物件 System.out.println(test2); System.out.println("------------------基於Class建立物件----------------------"); Object test3=Tst.newInstance(); //只能用於無參構函式,而且已經被棄用,不建議使用 System.out.println(test3); } } ``` ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/2020050301511214.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzYyNzExOA==,size_16,color_FFFFFF,t_70) **喜歡問問題的小朋友要來了?** 為什麼沒有getDeclaredConstructor方法和getDeclaredConstructors方法? 為什麼?為什麼? 有啊!! getDeclaredConstructor方法可以獲取到任何訪問許可權的構造器,而getConstructor方法只能獲取public修飾的構造器。具體不再測試。此外在構造器的物件內也有setAccessible(true);方法,並設定成true就可以操作了。 關於為什麼要使用private訪問許可權的構造器,使用這個構造器不就不能外部訪問了嘛,不也就無法進行例項化物件了嗎?無法在類的外部例項化物件正是私有構造器的意義所在,在單例模式下經常使用,整個專案只有一個物件,外部無法例項化物件,可以在類內的進行例項化並通過靜態方法返回,由於例項化的物件是靜態的,故只有一個物件,也就是單例的,這就是單例模式中的餓漢模式,不管是否呼叫,都建立一個物件。 ###### Method:方法物件 執行方法:Object invoke(Object obj, Object... args) 獲取方法名稱:String getName(); **舉例:** ```java package Test; public class Test { public String a; // 構造方法 public Test() { } public Test(String a) { this.a = a; } public void do_Something() { System.out.println("吃飯睡覺打豆豆"); } public String do_Something(String s) { System.out.println("吃飯睡覺打"+s); return "爽"; } } package Test; import java.lang.reflect.Method; public class Reflect { public static void main(String[] args) throws Exception { Class Tst = Test.class; Method[] mtd=Tst.getMethods(); for(Method m:mtd) System.out.println(m); } } ``` **執行結果:** ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200503020251837.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzYyNzExOA==,size_16,color_FFFFFF,t_70) **舉例2.0:** ```java package Test; public class Test { public String a; // 構造方法 public Test() { } public Test(String a) { this.a = a; } public void do_Something() { System.out.println("吃飯睡覺打豆豆"); } public String do_Something(String s) { System.out.println("吃飯睡覺打"+s); return "爽"; } } ``` ```java package Test; import java.lang.reflect.Method; public class Reflect { public static void main(String[] args) throws Exception { Class Tst = Test.class; Test test = new Test(); System.out.println("-----------------無參測試----------------------"); Method mtd1 = Tst.getMethod("do_Something");// 獲得無參的方法 Object return_Value = mtd1.invoke(test); // 呼叫方法 // 有返回值就得到一個值,沒有就得到一個null System.out.println(return_Value); System.out.println("-----------------含參測試----------------------"); Method mtd2 = Tst.getMethod("do_Something", String.class);// 獲得無參的方法 Object return_Value2 = mtd2.invoke(test, "張三"); // 呼叫方法 // 有返回值就得到一個值,沒有就得到一個null System.out.println(return_Value2); } } ``` **執行結果:** ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200503021136691.png) #### 總結 這時候又會有小朋友問: 為什麼要這麼麻煩,我直接呼叫不就好了? 不知你是否發現,從類的建立的方法的使用,所有的一切都是用的字串,那麼也就是說,我可以通過讀入資料,或者配置檔案的方式,建立類,呼叫方法。 **舉個簡單點的例子:** 就拿英雄聯盟這款遊戲來說,這遊戲三天兩頭的輪換一個娛樂模式,難道每次上線都要對原始碼進行修改,今天在Client呼叫“無限活力”,明天就要呼叫"魄羅大亂鬥”,每天就對著原始碼改?幾萬行的程式碼就這麼放心讓你改?除非你老闆想做空公司,故意的!必然不可能,這時候我們就算哪一個txt檔案,就放一行字串,用反射之後,只用改txt檔案不就完了!不用反射,是做不到用字串建立類,和執行方法(別抬槓,寫個if-else 或者 switch啥的)。 舉例可能不太恰當,一般不會使用txt,一般使用XML或者java配置檔案。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200503024155206.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzYyNzExOA==,size_16,color_FFFFFF,t_70) > **寫在最後:** > 我叫風骨散人,名字的意思是我多想可以`不低頭的自由生活`,可現實卻不是這樣。家境貧寒,總得向這個世界低頭,所以我一直在奮鬥,想`改變我的命運`給親人好的生活,希望`同樣被生活綁架的你`可以通過自己的努力改變現狀,深知成年人的世界裡沒有容易二字。目前是一名在校大學生,預計考研,熱愛程式設計,熱愛技術,喜歡分享,知識無界,希望我的分享可以幫到你! > 如果有什麼想看的,可以私信我,如果在能力範圍內,我會發布相應的博文! >感謝大家的閱讀!