一、導讀
反射的概念是由Smith在1982年首次提出的,主要是指程序可以訪問、檢測和修改它本身狀態或行為的一種能力。這一概念的提出很快引發了計算機科學領域關於應用反射性的研究。它首先被程序語言的設計領域所采用,並在Lisp和面向對象方面取得了成績。
在計算機科學領域,反射是指一類應用,它們能夠自描述和自控制。也就是說,這類應用通過采用某種機制來實現對自己行為的描述(self-representation)和監測(examination),並能根據自身行為的狀態和結果,調整或修改應用所描述行為的狀態和相關的語義。
概念很模糊,剛開始我們也沒必要深究。
二、對象的創建
“萬物皆對象”。對於面向對象語言,我們的操作都要基於對象,所以對象到底是怎么創建來的呢?為了方便說明,我以下面的自定義Person類來進行說明。
1 package bean; 2 3 public class Person { 4 private String name; 5 private int age; 6 public Person(){ 7 super(); 8 } 9 public Person(String name,int age){ 10 super(); 11 this.name = name; 12 this.age = age; 13 } 14 public String getName() { 15 return name; 16 } 17 public void setName(String name) { 18 this.name = name; 19 } 20 public int getAge() { 21 return age; 22 } 23 public void setAge(int age) { 24 this.age = age; 25 } 26 27 public static void function1(){ 28 System.out.println("function1"); 29 } 30 public void function2(){ 31 System.out.println("function2"); 32 } 33 public void function3(String s,int i){ 34 System.out.println(s+":::"+i); 35 } 36 @SuppressWarnings("unused") 37 private void function4(){ 38 System.out.println("function4"); 39 } 40 }
回到上面的問題:對象的創建,當然很簡單:
1 Person p1 = new Person(); 2 Person p2 = new Person("Lisi",20);
這樣我們就得到了p1和p2兩個對象。這是我們最常用也最簡單的對象創建方法:使用關鍵字new。但這種方法僅在開發的時候管用,考慮這樣的情況:當我們的程序開發完成之后,我們突然需要使用到某個類對程序進行功能擴展(就是辣么突然哈),這個時候我們無法再使用new,因為我們的程序已經封裝好了,即不能再修改源程序的情況下進行功能擴展,那么我們需要在開發的時候就考慮到這個問題,我們開發的時候就需要對外暴露接口,而我們只需知道被使用來擴展功能的類的類名就可以進行操作了,如何實現呢?(看完可能你有句“什么玩意兒”不知當講不當講)。沒看懂沒關系,現在你只需要知道除了使用new關鍵字來創建對象外,下面的方法也可以實現對象的創建:(也就是我們要講的java反射)
1 //1、獲取Class對象 2 String className = "bean.Person"; 3 Class<?> c = Class.forName(className); 4 5 //2、使用Class對象創建該類對象: 6 Object obj = c.newInstance();
解析:
我們關鍵字new類創建對象其實內部在進行:1、查找並加載Person.class文件進內存,並將該文件封裝成Class字節碼文件;2、根據Class字節碼文件進行類的創建;3、調用構造函數來初始化對象。
而這些步驟便是使用下面兩個步驟來完成的:首先使用Class類的forName()來獲取Class字節碼文件,然后使用Class字節碼文件實例化該類,即調用Instance()方法,而調用該方法后會自動調用構造函數進行初始化,完成對象的創建。
總結來說就是Person p = new Person();等價於Class.forName("bean.Person").newInstance();
一些細節問題:
1、forName()方法中傳入的是類的名字:Person,但是我們需要在類名前寫上包名,bean.Person。因為不同包中可能有相同的類名,我們需要指明包才能找到類。
2、 如果指定的類中沒有空參數函數或者該類需要指定的構造函數進行初始化時,即我們需要使用到帶有參數的構造函數時,就不能再使用Class對象的newInstance()來創建對象了:我們需要這樣來創建對象:
1 Class c = Class.getName("bean.Person"); 2 //1、獲取這個帶有參數的構造器 3 Constructor<?> con = c.getConstructor(String.class,int.class); 4 //2、使用構造器來創建: 5 Object obj = con.newInsatnce("LiSi",20);
這樣等價於Person p = new Person(“LiSi”,20);
三、Java"反射包"
正如上面講到的對象的創建,Java專門提供了一個包:Java.lang.reflect,用於完成反射:如通過Class對象來獲取類的成員、類中的方法等等。

四、獲取類的字段
假設現在我們需要訪問Person類中的字段:name和age。
1、獲取:我們可以使用Class對象中的getField()方法和getDeclaredField()來訪問,兩者的區別在於:getField()返回的是公有的字段和父類中的字段,而getDeclaredField()返回的是私有化的字段。
2、使用:我們獲取到了字段就可以進行使用了,但在使用之前我們需要知道:1)該類對象 2)使用的字段是否有訪問權限。
1)使用forName()創建一個對象;
2)如果使用的字段無訪問權限,即公有,則可以直接使用;如果該字段私有,則我們需要使用setAccessible(true)來取消權限設置,才能進行訪問。
看具體實現代碼:
1 package reflect; 2 3 import java.lang.reflect.Field; 4 5 public class getFieldDemo { 6 public static void main(String[] args) throws Exception { 7 String className = "bean.Person"; 8 Class<?> c = Class.forName(className); 9 10 //獲取私有字段 11 Field nameField = c.getDeclaredField("name"); 12 Field ageField = c.getDeclaredField("age"); 13 14 //使用字段(使用之前我們需要一個該類對象) 15 Object obj = c.newInstance(); 16 17 //使用set()方法設置字段值 18 nameField.setAccessible(true); 19 ageField.setAccessible(true);//暴力訪問 20 nameField.set(obj, "張三"); 21 ageField.set(obj,20); 22 23 //打印查看效果 24 System.out.println("獲取到的字段:"); 25 System.out.println("name:"+nameField); 26 System.out.println("age:"+ageField); 27 System.out.println("字段設置的值:name="+nameField.get(obj)+",age="+ageField.get(obj)); 28 } 29 }
打印結果:

五、獲取類中的方法
假設我們需要獲取到Person類中的4個方法: 類似獲取字段,我們首先要明確該方法的訪問權限和是否有參數。
1、獲取
我們使用getMethods()和getDeclaredMethods()來:前者獲取公有及父類中的方法,后者獲取私有方法。而獲取一個方法則使用getMethod(String name,Class<?>... parameterTypes)和getDeclaredMethod(String name,Class<?>... parameterTypes)來獲取。
2、調用
使用Method類中的invoke(object obj,object args...)方法來調用有參數的方法的底層方法。
具體實現代碼:
1 package reflect; 2 3 import java.lang.reflect.Method; 4 5 public class getMethodDemo { 6 public static void main(String[] args) throws Exception { 7 String className = "bean.Person"; 8 Class<?> c = Class.forName(className); 9 10 //獲取公共方法: 11 Method[] pubMethods = c.getMethods(); 12 13 //獲取私有方法: 14 Method[] priMethods = c.getDeclaredMethods(); 15 16 17 //獲取單個方法:按方法名和參數獲取 18 19 //獲取單個の靜態方法:function1 20 Method staMethod = c.getMethod("function1",null); 21 //獲取單個の無參數方法:function2 22 Method nullMethod = c.getMethod("function2",null); 23 //獲取單個の有參數方法:function3 24 Method moreMethod = c.getMethod("function3",String.class,int.class); 25 //獲取單個の私有方法:function4 26 Method priMethod = c.getDeclaredMethod("function4",null); 27 28 //打印查看效果 29 System.out.println("[Person類的公共方法及父類方法:]"); 30 for(Method m:pubMethods){ 31 System.out.println(m); 32 } 33 System.out.println("[Person類的私有方法:]"); 34 for(Method m:priMethods){ 35 System.out.println(m); 36 } 37 System.out.println("[按方法名和參數類型獲取的方法4個方法:]"); 38 System.out.println(staMethod); 39 System.out.println(nullMethod); 40 System.out.println(moreMethod); 41 System.out.println(priMethod); 42 } 43 }
打印結果:

1 package reflect; 2 3 import java.lang.reflect.Method; 4 5 public class invokeDemo { 6 public static void main(String[] args) throws Exception { 7 String className = "bean.Person"; 8 Class<?> c = Class.forName(className); 9 10 //獲取有參數的方法:function3 11 Method moreMethod = c.getDeclaredMethod("function3",String.class,int.class); 12 13 //使用之前我們需要創建一個該類對象: 14 Object obj = c.newInstance(); 15 moreMethod.setAccessible(true);//設置訪問權限 16 Object value = moreMethod.invoke(obj,"李四",20); 17 18 //打印查看效果 19 System.out.println(value); 20 } 21 }
打印結果:

六、進階:反射機制的運用實例の我的電腦沒USB
我們以下面這個類:myComputer來說明:
1 package bean; 2 /* 3 * 模擬一台具有開機關機功能的電腦. 4 */ 5 public class myComputer { 6 public void run(){ 7 System.out.println("Running..."); 8 } 9 public void close(){ 10 System.out.println("Closed!"); 11 } 12 }
現在假設你開發出來了一台電腦:沒錯就是上面myComputer(唉,別走回來)。
假設你開發的這台電腦問世的時候只能開機和關機,現在你覺得這台電腦的功能有一點單調了(一點?),所以你希望這台電腦可以實現:鼠標和鍵盤的連接,並可以使用鼠標和鍵盤的功能。那么要怎么實現呢?
我們都知道接鼠標或鍵盤需要有USB接口才能實現,所以我們需要myComputer具備USB接口功能:我們可以使用接口Interface實現。然后我們需要讓USB接口和鼠標或鍵盤相匹配或者說連接起來:則我們讓鼠標和鍵盤可以實現USB接口。然而問題的關鍵在於我在開發myComputer時並不知道我有什么鼠標啊鍵盤啊什么的,更別說拿來用了。該如何讓鼠標或鍵盤等外部設備為我所用呢?
現在我們唯一能知道的就是鼠標啊、鍵盤啊等設備的名字,它們內部是什么都不清楚,怎么創建一個鼠標或鍵盤對象來調用它們的方法呢?如果你好好看了上面的反射,那么你應該可以實現:我們只需要使用Class.forName(className).newInstance();就行了。我們只需要知道className即類的名字就能創建這個類,包括獲取類的字段、類的方法等等關於該類的信息,這就是反射的強大之處。
七、進階:反射機制的運用實例の我的電腦有USB啦啦啦
1、首先我們需要對初代myComputer就行改進:實現USB接口
myComputer2:
1 package bean; 2 /* 3 * 模擬一台具有開機關機功能的電腦. 4 */ 5 public class myComputer2 { 6 public void run(){ 7 System.out.println("Running..."); 8 } 9 public void close(){ 10 System.out.println("Closed!"); 11 } 12 //升級后的2代計算機:USB功能 13 public void useUSB(USB usb){ 14 if(usb != null){//如果設備連接上,則開始使用設備功能 15 usb.connection(); 16 usb.close(); 17 } 18 } 19 }
USB接口:
1 package bean; 2 /* 3 * 用於描述USB接口 4 */ 5 public interface USB { 6 public void connection();//設備連接 7 public void close();//設備斷開 8 }
設備:鼠標和鍵盤
1 package bean; 2 /* 3 * 鼠標 4 */ 5 public class Mouse implements USB { 6 7 @Override 8 public void connection() { 9 System.out.println("鼠標正在使用..."); 10 } 11 12 @Override 13 public void close() { 14 System.out.println("鼠標已斷開連接!"); 15 } 16 17 }
1 package bean; 2 /* 3 * 鍵盤 4 */ 5 public class keyboard implements USB { 6 7 @Override 8 public void connection() { 9 System.out.println("鍵盤正在使用..."); 10 } 11 12 @Override 13 public void close() { 14 System.out.println("鍵盤已斷開連接!"); 15 } 16 17 }
2、USB的使用測試:
我們現在來測試一下:
1 package reflect; 2 3 import bean.USB; 4 import bean.myComputer2; 5 6 public class test { 7 public static void main(String[] args) throws Exception { 8 myComputer2 mc = new myComputer2(); 9 10 //我們只需要知道封裝設備的類名即可 11 String className = "bean.Mouse"; 12 Class<?> c = Class.forName(className); 13 Object obj = c.newInstance(); 14 15 USB usb = (USB)obj; 16 mc.useUSB(usb); 17 } 18 }
test的測試結果:

很明顯,"鼠標"接上myComputer2后可以正常使用。
3、測試改進:
對於上面的測試類test中的className的值我們是手動輸入的,這不符合后來開發者的需求,因為我們開發的時候可能根本不知道有鼠標這種設備,或者說要是將來有新的設備連接我們該怎么辦呢?
這時候我們需要使用到配置文件,即使用IO流來對我們使用的設備進行后台管理。
看具體改進代碼:
package reflect; import java.io.File; import java.io.FileInputStream; import java.util.Properties; import bean.USB; import bean.myComputer2; public class test2 { public static void main(String[] args) throws Exception { myComputer2 mc = new myComputer2(); //利用IO流,使用配置文件傳入類名: File config = new File("tempFile\\usb.config"); FileInputStream fis = new FileInputStream(config); Properties prop = new Properties(); prop.load(fis); String className = null; className = prop.getProperty("usb"); Class<?> c = Class.forName(className); Object obj = c.newInstance(); USB usb = (USB)obj; mc.useUSB(usb); } }
現在我的配置文件傳入什么設備,那么這台電腦就會使用什么設備:
如我的配置文件為:

這里我連接的設備是keyboard即鍵盤,那么運行結果會是什么呢?

沒毛病O(∩_∩)O歡迎指正!
