版權聲明:轉載請說明出處:http://blog.csdn.net/yhaolpz
在我的上篇文章模擬JDK動態代理實現中涉及到了反射機制,學無止境,在此系統的學習一下java中的反射機制。首先給出Java反射機制的定義:
JAVA反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。
從以上的描述中可以看出Java中的動態特性,那么Java屬于動態語言嗎?一般而言說到動態語言,大致認同的一個定義是:“程序運行時,允許改變程序結構或變量類型,這種語言稱為動態語言”。從這個觀點看,Perl,Python,Ruby是動態語言,C++,Java,C#不是動態語言。
盡管在這樣的定義與分類下Java不是動態語言,但是它卻有著一個非常突出的動態相關機制,即反射機制。通過Java反射機制可以在運行時才加載class,得到這個類的類類型的相關信息,生成實體對象、或對其成員變量設值、喚起方法。
總上,Java反射機制主要提供了以下功能: 在運行時判斷任意一個對象所屬的類;在運行時構造任意一個類的對象;在運行時判斷任意一個類所具有的成員變量和方法;在運行時調用任意一個對象的方法;生成動態代理。
類的類類型(com.lang.Class類的實例對象)
當Java虛擬機加載一個類時,會隱含的創建描述這個類的Class實例,通常把這個Class類的實例對象叫做這個類的類類型( class type),這樣更易理解。下面為一個簡單示例:
package com.reflact;public class Test1 { public static void main(String args[]){ Car car1 = new Car(); }}1234567812345678我們都知道 car1 是 Car類 的一個實例對象,那么 Car 這個類到底是什么呢? 這個 Car 類 本身也是一個對象 。在面向對象的世界里,萬事萬物皆對象,類也是對象,是Class類的實例對象。為了更易理解,我們把這個對象叫做該類的類類型(class type)。對比 car1 和 Car ,可以理解成car1 對象是按照Car這個類的類型來實例化的,同樣Car類也有它自己的 類類型,可以通過以下三種方式獲取Car類的類類型(class type):
package com.reflact;public class Test1 { public static void main(String args[]){ Car car1 = new Car(); Class c1 = Car.class; Class c2 = car1.getClass(); try { Class c3 = Class.forName("com.reflact.Car"); } catch (ClassNotFoundException e) { e.PRintStackTrace(); } }}123456789101112131415123456789101112131415c1、c2和c3都是Car類的類類型(class type),其中第一種方式和第三種方式通過類來獲取類類型,第二種是通過類的實例化對象來獲取類類型。那么c1、c2、c3是否相等呢?做以下測試:
package com.reflact;public class Test1 { public static void main(String args[]) throws ClassNotFoundException { Car car1 = new Car(); Class c1 = Car.class; Class c2 = car1.getClass(); Class c3 = Class.forName("com.reflact.Car"); System.out.println(c1 == c2); System.out.println(c1 == c3); }}1234567891011121312345678910111213測試結果:
按照類類型的角度很容易理解,c1、c2、c3都代表了Car類的類類型,所以它們是同一個東西,什么東西呢?上面提到類是java.lang.Class類的實例對象,也就是說c1、c2、c3都是Class類的實例對象,而一個類只能是Class類的一個實例化對象,所以c1、c2、c3是Class類的同一個實例對象,所以它們相等。
通過類的類類型創建類的實例對象
在初學Java創建對象的方式時知道有顯式創建對象和隱式創建對象兩類創建方式,其中顯式創建對象有以下四種方式:
用new語句創建對象運用反射手段調用對象的clone()方法運用反序列化手段其中運用反射手段就是通過類的類類型來創建類的實例對象。示例如下:
Class c1 = Car.class; try { Car car2 = (Car)c1.newInstance(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalaccessException e) { // TODO Auto-generated catch block e.printStackTrace(); }12345678910111234567891011那么通過反射手段來創建類的實例對象相比 new語句創建對象有什么優點呢?比如Class c3 = Class.forName(“com.reflact.Car”);這個語句可以實現類的動態加載,首先我們來了解下類的靜態加載和動態加載:
類的靜態加載:類在編譯時就要提供類的動態加載:類在編譯時可以不存在而通過反射手段可以實現類的動態加載,所以反射就有了相當重要的作用,比如某些框架中(struts,spring)使用反射來根據配置文件中的類路徑來找到類的位置,然后動態執行你事先實現的方法;再比如在json序列化中,需要使用反射找到你類的所有成員,然后在動態獲取這些成員的值,然后生成一個類似于{propertyName:value}的json字符串。
基本數據類型的類類型
在上面涉及到的是Car類的類類型,其實基本數據類型也有自己的類類型。如下所示:
Class a = int.class; Class b = String.class; Class c = double.class; Class d = Double.class; Class e = void.class; Class f = Void.class; System.out.println(a.getName()); System.out.println(b.getName()); System.out.println(c.getName()); System.out.println(d.getName()); System.out.println(e.getName()); System.out.println(f.getName());123456789101112123456789101112輸出結果:
到現在已經了解了什么是類的類類型、怎么獲得類的類類型、通過類的類類型可以創建類的實例對象等,那么通過類的可以獲得哪些信息呢?
通過類類型獲取類的方法信息
package com.reflact;import java.lang.reflect.Method;public class ClassUtil { public static void printClassMethodMessage(Object obj){ //通過類的實例對象獲取類的類類型 Class c = obj.getClass(); //獲取類的名稱 System.out.println("類的名稱:"+c.getName()); //獲取類的所有public方法,包括父類繼承而來的 Method[] ms = c.getMethods(); //獲取該類自身聲明的所有方法 Method[] ms2 = c.getDeclaredMethods(); for(int i=0;i<ms.length;i++){ //獲取方法的返回值類型的類類型 Class returnType = ms[i].getReturnType(); System.out.print(returnType.getName()+" "); //得到方法名稱 System.out.print(ms[i].getName()+"("); //得到方法參數列表的類型的類類型 Class[] paramTypes = ms[i].getParameterTypes(); for(Class param:paramTypes){ System.out.print(param.getSimpleName()+", ");//過濾掉包名的類名 } System.out.print(")"); System.out.println(); } }}12345678910111213141516171819202122232425262728293031323334351234567891011121314151617181920212223242526272829303132333435測試結果如下:
通過類類型獲取類的成員變量信息
package com.reflact;import java.lang.reflect.Field;public class ClassUtil { public static void printClassFieldMessage(Object obj){ //通過類的實例對象獲取類的類類型 Class c = obj.getClass(); //獲取類的名稱 System.out.println("類的名稱:"+c.getName()); //獲取所有public成員變量信息 Field[] fs = c.getFields(); //獲取該類自身聲明的所有成員變量 Field[] fs2 = c.getDeclaredFields(); for(Field f:fs){ //獲取成員變量類型的類類型 Class fieldType = f.getType(); //獲取成員變量的類型 String typeName = fieldType.getName(); //獲取成員變量的名稱 String fieldName = f.getName(); System.out.println(typeName+" "+fieldName); } }}12345678910111213141516171819202122232425262728293031321234567891011121314151617181920212223242526272829303132測試類:
package com.reflact;public class Test1 { public static void main(String args[]) throws ClassNotFoundException { Car car1 = new Car();// ClassUtil.printClassMethodMessage(car1); ClassUtil.printClassFieldMessage(new Integer(1)); }}123456789123456789測試結果:
通過類類型獲取類的構造函數信息
package com.reflact;import java.lang.reflect.Constructor;public class ClassUtil { public static void printConMessage(Object obj){ //通過類的實例對象獲取類的類類型 Class c = obj.getClass(); //獲取類的名稱 System.out.println("類的名稱:"+c.getName()); //獲取所有的public的構造函數 Constructor[] cs = c.getConstructors(); //獲取所有構造函數 Constructor[] cs2 = c.getDeclaredConstructors(); for(Constructor con:cs2){ //獲取構造函數名稱 System.out.print(con.getName()+"("); //獲取構造函數的參數列表類型的類類型 Class[] paramTypes = con.getParameterTypes(); for(Class cls:paramTypes){ System.out.print(cls.getName()+", "); } System.out.print(")"); System.out.println(); } }}1234567891011121314151617181920212223242526272829303112345678910111213141516171819202122232425262728293031測試類:
package com.reflact;public class Test1 { public static void main(String args[]) throws ClassNotFoundException { Car car1 = new Car();// ClassUtil.printClassMethodMessage(car1);// ClassUtil.printClassFieldMessage(new Integer(1)); ClassUtil.printConMessage(new String("hello")); }}1234567891012345678910測試結果:
以上示例通過類的類類型獲取了類的方法、成員變量以及構造函數信息,通過類的類類型還可以獲得除此之外的其他信息,比如類的接口信息、訪問權限、包名等等,可查閱文檔了解。
通過類類型獲取方法后調用方法
package com.reflact;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class Test1 { public static void main(String args[]){ //獲取類的類類型 A a1 = new A(); Class c = a1.getClass(); //獲取方法 名稱和參數確定 try { //參數列表為可變數組 可用以下兩種方式表示 Method md = c.getMethod("print", new Class[]{int.class,int.class}); Method md2 = c.getMethod("print", int.class,int.class); //方法的反射操作,參數列表為可變數組,同樣以下兩種方式都可表示 Object returnValue = md.invoke(a1, new Object[]{10,20});//無返回值則返回null Object returnValue2 = md.invoke(a1, 10,20); Method md3 = c.getMethod("print"); md3.invoke(a1); } catch (Exception e) { e.printStackTrace(); } }}class A { public void print(){ System.out.println("null........."); } public void print(int a, int b) { System.out.println(a+b); } public void print(String a, String b) { System.out.println(a.toUpperCase()+","+b.toLowerCase()); }}12345678910111213141516171819202122232425262728293031323334353637381234567891011121314151617181920212223242526272829303132333435363738測試結果:
學到這里已經明顯和之前動態代理部分的知識關聯起來了,Java中的反射機制真的特別重要,最近學的spring中依賴注入和AOP的底層實現都是建立在反射機制上的。對反射機制理解透徹有助于我們去理解那些框架,當然在我認為并框架不重要,學好這些框架底層的實現原理比學會框架的使用重要的多的多,這也能決定一個程序員思考的深度,不至于被淹沒在花樣百出的框架中。關于Java的反射機制用處十分廣泛,比如:
通過反射機制了解集合泛型的本質
首先來看以下簡單示例:
package com.reflact;import java.util.ArrayList;public class Test2 { public static void main(String[]args){ ArrayList list1 = new ArrayList(); ArrayList<String> list2 = new ArrayList<String>(); list1.add("hello");//編譯通過 list1.add(10);//編譯通過 list2.add("hello");//編譯通過 list2.add(10);//編譯出錯 }}12345678910111213141234567891011121314如果定義了ArrayList的泛型為String,就只能添加Sting類型的對象;如果沒有指定泛型,則可添加多種類型。上例中list1沒有指定泛型,list2指定了String泛型,那么他們的類類型是否相等呢?
package com.reflact;import java.util.ArrayList;public class Test2 { public static void main(String[]args){ ArrayList list1 = new ArrayList(); ArrayList<String> list2 = new ArrayList<String>(); Class c1 = list1.getClass(); Class c2 = list2.getClass(); System.out.println(c1==c2); }}123456789101112123456789101112測試結果:
由于反射操作是在編譯操作完成之后進行的,也就是說編譯之后集合的泛型是去泛型化的。那指定泛型有什么作用呢?答案就是Java的集合泛型是用來防止錯誤輸入的,只在編譯階段有效。一般來說一個集合內存放的數據類型是相同的,所以用泛型來增強程序的健壯性,減少出錯率。那我們能不能繞過編譯階段往規定泛型為String類型的集合里添加int類型呢?答案也是可以的,這就用到了上面的方法反射操作。
測試代碼如下:
package com.reflact;import java.lang.reflect.Method;import java.util.ArrayList;public class Test2 { public static void main(String[]args){ ArrayList<String> list = new ArrayList<String>(); Class c = list.getClass(); try { Method m = c.getMethod("add", Object.class); m.invoke(list, 100); m.invoke(list, 200); } catch (Exception e) { e.printStackTrace(); } System.out.println(list.size()); }}1234567891011121314151617181912345678910111213141516171819測試結果:
以上示例通過方法反射操作,成功的將int類型數據放入了泛型規定為String類型的集合中。
新聞熱點
疑難解答