反射:Class類、反射概念、類中各成員的反射、反射的應用、數組的反射

1  反射的基礎Class

      Person類代表人,它的實例對象就是張三,李四這樣一個個具體的人

      在Java程序中的各個Java類屬於同一類事物,描述這類事物的Java類名就是Class

 

      Class類代表Java類,它的各個實例對象又分別對應什麼呢?
      對應各個類在內存中的字節碼。例如,Person類的字節碼,ArrayList類的字節碼等等。

 

      一個類被類加載器加載到內存中,佔用一片存儲空間,這個空間裏面的內容就是類的字節碼,不同的類的字節碼是不同的,所以它們在內存中的內容是不同的,這一個個的空間可分別用一個個的對象來表示,這些對象顯然具有相同的類型,這個類型就是Class

      Class類描述了哪些方面的信息呢:類的名字,類的訪問屬性,類所屬於的包名,字段名稱的列表、方法名稱的列表,等等。

      學習反射,首先就要明白Class這個類。

 

如何得到各個字節碼對應的實例對象(Class類型)
      1,類名.class,例如,System.class
      2,對象.getClass(),例如,new Date().getClass()
      3,Class.forName("類名"),例如,Class.forName("java.util.Date");


九個預定義Class實例對象(八大基本類型+void):

      Byte.TYPE,Integer.TYPE,Short.TYPE,Long.TYPE,Float.TYPE,Double.TYPE,Character.TYPE,Boolean.TYPE,Void.TYPE。(返回對應的基本數據類型的字節碼Class對象)

      這些對象通過基本數據類型調用,被public static final修飾。

      這些對象僅能通過下列聲明爲public static final的變量訪問

 

注意:

      1Int.class == Integer.TYPE

      2、數組類型的Class實例對象Class.isArray()true

總之,只要是在源程序中出現的類型,都有各自的Class實例對象,例如,int[]void…

 

示例:

[java] view plain copy
  1. public class ReflectDemo{  
  2.     public static void main(String[] args) throws Exception {  
  3.         String str = "abc";  
  4.           
  5.         //三種獲取字節碼對象的方式:  
  6.         Class clazz1 = String.class;  
  7.         Class clazz2 = str.getClass();  
  8.         Class clazz3 = Class.forName("java.lang.String" );  
  9.           
  10.         System. out.println(clazz1 == clazz2);//true  
  11.         System. out.println(clazz2 == clazz3);//true  
  12.           
  13.         System. out.println(clazz1.isPrimitive());//false  
  14.         System. out.println(int.class.isPrimitive()); //true  
  15.           
  16.         //這裏注意,基本類型int和包裝類Integer的Class對象(字節碼)不同。  
  17.         System. out.println(int.class == Integer.class);//false  
  18.           
  19.         //Integer.TYPE,表示基本類型int的Class對象。  
  20.         System. out.println(int.class == Integer.TYPE);//true  
  21.           
  22.         System. out.println(int[].class.isPrimitive());//false  
  23.         System. out.println(int[].class.isArray());//true  
  24.     }  
  25. }  

2  反射的概念

      反射就是把Java類中的各種成分映射成相應的java

      例如,一個Java類中用一個Class類的對象來表示,一個類中的組成部分:成員變量,方法,構造方法,包等等信息也用一個個的Java類來表示。就像汽車是一個類,汽車中的發動機,變速箱等等也是一個個的類。

      表示java類的Class類顯然要提供一系列的方法,來獲得其中的變量,方法,構造方法,修飾符,包等信息,這些信息就是用相應類的實例對象來表示,它們是FieldMethodContructorPackage等等。

     一個類中的每個成員都可以用相應的反射API類的一個實例對象來表示,通過調用Class類的方法可以得到這些實例對象。


3  構造方法的反射Constructor

Constructor類代表某個類中的一個構造方法。

Constructor對象代表一個構造方法,Constructor對象上會有的方法有:得到方法名字,得到所屬於的類,產生實例對象。

 

得到某個類所有的構造方法:
      例子:Constructor[] constructors= Class.forName("java.lang.String").getConstructors();

得到某一個構造方法:
        例子:Constructor constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);  
//獲得方法時要用到類型

注意:

      一個類有多個構造方法,根據getConstructors() 參數的個數和類型,區分清楚想得到其中的哪個方法

      例如,Class.getMethod(name,Class... args)中的args參數就代表所要獲取的那個方法的各個參數的類型的列表。

重點:getConstructors() 參數類型用什麼方式表示?用Class實例對象。

重點:getConstructors()只能獲得被public修飾的構造函數。沒有public修飾的可以通過getDeclaredConstructor()獲得。

 

創建實例對象:

      通常方式:String str = new String(new StringBuffer("abc"));

      反射方式: String str = (String)constructor.newInstance(new StringBuffer("abc"));

                           //調用獲得的方法時要用到上面相同類型的實例對象

Class.newInstance()方法:
      例子:String obj = (String)Class.forName("java.lang.String").newInstance(); //該方法內部先得到默認的構造方法,然後用該構造方法創建實例對象。

 

代碼示例:

[java] view plain copy
  1. public class ContructroReflect {  
  2.     public static void main(String[] args) throws Exception {  
  3.           
  4.         //根據參數列表中傳遞的Class對象,獲取相應的構造函數。  
  5.         Constructor constructor = String.class.getConstructor(StringBuffer.class);  
  6.           
  7.         //使用此Constructor對象表示的構造方法來創建該構造方法的聲明類的新實例,  
  8.         //並用指定的初始化參數初始化該實例。  
  9.         String str = (String)constructor.newInstance( new StringBuffer("abc" ));  
  10.           
  11.         System. out.println(str.charAt(2));  
  12.         //結果:c  
  13.     }  
  14. }  

4  成員變量的反射(暴力反射)

Field類代表某個類中的一個成員變量

問題:

得到的Field對象是對應到類上面的成員變量,還是對應到對象上的成員變量?

類只有一個,而該類的實例對象有多個,如果是與對象關聯,哪關聯的是哪個對象呢?

所以字段field X 代表的是X的定義,而不是具體的X變量。

 

getField():獲取公有字段的Class實例。

getDeclaredField()獲取私有字段的Class實例。

fieldX.setAccessible( true):可以使對象rp中的私有成員變量X,可以從外部被訪問,即fieldX.get( rp),這稱爲暴力反射。

 

代碼示例:

[java] view plain copy
  1. import java.lang.reflect.Constructor;  
  2. import java.lang.reflect.Field;  
  3.   
  4. class ReflectPoint {  
  5.     private int x;  
  6.     public int y ;  
  7.       
  8.     public ReflectPoint(int x, int y) {  
  9.          super();  
  10.          this.x = x;  
  11.          this.y = y;  
  12.     }  
  13. }  
  14.   
  15. public class FieldReflect{  
  16.     public static void main(String[] args) throws Exception {  
  17.         ReflectPoint rp = new ReflectPoint(35);  
  18.           
  19.         Field fieldY = rp.getClass().getField( "y");//獲取字段Y的Class實例  
  20.         /*  fieldY值是多少?是5?錯! 
  21.             fieldY不是對象身上的變量,而是類上的定義, 
  22.             要用它去取某個對象上對應的值。 */  
  23.           
  24.         System. out.println(fieldY.get(rp));//用fieldY對象獲取rp上相應的值  
  25.         //結果:5  
  26.           
  27.         Field fieldX = rp.getClass().getDeclaredField("x");//獲取私有字段  
  28.           
  29.         fieldX.setAccessible(true); //使private類型的成員變量也可以被獲取值,稱爲暴力反射  
  30.           
  31.         System. out.println(fieldX.get(rp));//暴力反射後,私有的成員變量也可以被取值  
  32.           
  33.         /*  getField方法只能獲取公有字段的Class實例。 
  34.             getDeclaredField方法可以獲取私有字段的Class實例。 
  35.              
  36.             然後通過setAccessible(true)方法使私有的成員變量也可以被訪問到。  
  37.             這稱爲暴力反射(private修飾的成員變量只能在本類內訪問)。*/  
  38.     }  
  39. }  

暴力反射

通過調用字段fieldXsetAccessible( true) 方法,可以使對象rp中的私有成員變量X,可以從外部被訪問,即fieldX.get( rp),這稱爲暴力反射。


5  成員方法的反射

Method類代表某個類中的一個成員方法。


得到類中的某一個方法:
      例子:Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
調用方法:
      通常方式:char x = str.charAt(1));
      反射方式:char x = charAt.invoke(str, 1));  
//調用charAt實例代表的方法,調用者是str字符串,傳遞參數1

注意:

      如果傳遞給Method對象的invoke()方法的第一個參數爲null,說明該Method對象對應的是一個靜態方法!

jdk1.4jdk1.5invoke方法的區別:

      Jdk1.4public Object invoke(Object obj,Object[] args)
      Jdk1.5public Object invoke(Object obj,Object... args)  //可變參數

代碼示例:

[java] view plain copy
  1. import java.lang.reflect.Method;  
  2. public class MethodReflect{  
  3.     public static void main(String[] args) throws Exception {  
  4.         String str = "abc";  
  5.           
  6.         //獲取charAt(int)方法的Class實例。  
  7.         Method methodCharAt = String.class.getMethod("charAt"int.class );  
  8.           
  9.         // invoke(str, 1):調用str中的當前方法,參數是1.  
  10.         System. out.println(methodCharAt.invoke(str, 1));//結果:b  
  11.         System. out.println(methodCharAt.invoke(str, new Object[]{2}));//結果:c  
  12.     }  
  13. }  

6  用反射方式執行某個類中的main方法

寫一個程序A,該程序能根據用戶提供的類名B,去執行B類中的main方法。

通過給Amain方法設置傳入的參數,來指定B類的類名。

 

命令行下給main方法傳遞參數:B類的類名(包括包路徑)java運行命令之後。

Eclipse中給main方法傳遞參數:右擊Run As-->點擊Run Configurations-->點擊Arguments選項卡

 

問題:

      啓動Java程序的main方法的參數是一個字符串數組,

      public static void main(String[] args)

      通過反射方式來調用這個main方法時,如何爲invoke方法傳遞參數呢?

      jdk1.5的語法,整個數組是一個參數,而按jdk1.4的語法,數組中的每個元素對應一個參數,當把一個字符串數組作爲參數傳遞給invoke方法時,javacjdk1.4的語法進行處理,即把數組拆開成若干個單獨的參數。

      所以,在給main方法傳遞參數時,不能使用代碼mainMethod.invoke(null,new String[]{“xxx”})javac只把它當作jdk1.4的語法進行理解,即把數組拆開成若干個單獨的參數,而不把它當作jdk1.5的語法解釋(整個數組是一個參數),因此會出現參數類型不對的問題。

 

解決該問題的方法:

方法一:mainMethod.invoke(null, new Object[]{new String[]{"xxx"}});  //多加一個數組。

方法二:mainMethod.invoke(null, (Object)new String[]{"xxx"});  //(Object) 是告訴編譯器,傳遞的是一個對象不是一個數組,就不會把數組拆開成若干個單獨的參數了。

 

代碼示例:

[java] view plain copy
  1. package mypkg;  
  2. import java.lang.reflect.Method;  
  3.   
  4. public class MainReflect{  
  5.     public static void main(String[] args) throws Exception {  
  6.         String startingClassName = args[0];  
  7.           
  8.         Method mainMethod = Class.forName(startingClassName).getMethod("main",String[].class);  
  9.           
  10.       //mainMethod.invoke(null, new Object[]{new String[]{"111","222","333"}}); //方法一  
  11.         mainMethod.invoke(null, (Object)new String[]{"111","222","333"}); //方法二  
  12.     }  
  13. }  
  14.   
  15. class TestArguments{  
  16.     public static void main(String[] args) {  
  17.         for(String arg : args){  
  18.             System. out.println(arg);  
  19.         }  
  20.     }  
  21. }  

命令行下編譯、運行結果:



7  數組的反射

7.1 數組的反射特點

具有相同維數和元素類型的數組屬於同一個類型,即具有相同的Class實例對象(此處比較與值無關)

代表數組的Class實例對象的getSuperClass()方法,返回的父類爲Object類對應的Class

 

基本類型的一維數組可以被當作Object類型使用,不能當作Object[]類型使用;

非基本類型的一維數組,既可以當做Object類型使用,又可以當做Object[]類型使用。

 

代碼及註釋:

[java] view plain copy
  1. public class ArrayReflect {  
  2.     public static void main(String[] args) throws Exception {  
  3.         int [] a1 = new int[3];  
  4.         int [] a2 = new int[4];  
  5.         int [][] a3 = new int[2][3];  
  6.         String[] a4 = new String[3];  
  7.   
  8.         System.out.println(a1.getClass() == a2.getClass());//結果:true  
  9.         System.out.println(a1.getClass() == a4.getClass());//結果:false  
  10.         System.out.println(a1.getClass() == a3.getClass());//結果:false  
  11.           
  12.           
  13.         System. out .println(a1.getClass().getName());//結果:[I  
  14.         /* Class類的getName方法返回值中[表示數組的深度, 
  15.         [表示一維數組,[[表示二維數組。剩下的內容則表示數組類型,例如I表示int類型. */  
  16.           
  17.         System. out .println(a1.getClass().getSuperclass().getName());//結果:java.lang.Object  
  18.         System. out .println(a4.getClass().getSuperclass().getName());//結果:java.lang.Object  
  19.           
  20.           
  21.         Object aObj1 = a1; //int[]數組是Object,int不是Object。  
  22.         Object aObj2 = a4;  
  23.           
  24.         //Object[] aObj3 = a1; //a1是int類型的數組,int不是Object,因此編譯就會報錯  
  25.           
  26.         //a3是數組的數組,元素也是一維數組,父類是Object,因此不會報錯  
  27.         Object[] aObj4 = a3;  
  28.           
  29.         //a4是String的數組,String父類是Object,因此不會報錯  
  30.         Object[] aObj5 = a4;  
  31.     }  
  32. }  

7.2  Arrays類的asList()方法處理int[]String[]時的差異

JDK1.4中爲 Arrays.asList(Object[] a);

JDK1.5中爲 Arrays.asList(T... a);

所以 as.List( new int[]{1,2,3}) 中,數組元素的類型是int,而int不是Object,所以不能打印出數組內容。


代碼及註釋:

[java] view plain copy
  1. import java.util.Arrays;  
  2.   
  3. public class ArrayReflect2 {  
  4.     public static void main(String[] args) throws Exception {  
  5.         int [] a5 = new int[]{1,2,3};  
  6.         String[] a6 = new String[]{"a" ,"b" ,"c" };  
  7.           
  8.         //直接使用System.out.println無法打印出數組的內容  
  9.         System. out .println(a5); //結果:[I@18a992f  
  10.         System. out .println(a6); //結果:[Ljava.lang.String;@4f1d0d  
  11.          
  12.         //通過Arrays.asList方法打印出數組的內容  
  13.         System. out .println(Arrays.asList(a5));  
  14.         /* 結果:[[I@18a992f] 
  15.         原因是因爲JDK1.4中爲Arrays.asList(Object[] a),JDK1.5中爲Arrays.asList(T... a)。 
  16.         a5是int[]類型,元素類型int不是Object,JDK1.4中的asList方法處理不了,JDK1.5可以處理。 
  17.         但是JDK1.5將 int數組整體作爲一個參數進行處理。 
  18.         因此最終結果就是將 int[]進行了封裝,結果類型也就成了[[I。 */  
  19.           
  20.         System. out .println(Arrays.asList(a6));  
  21.         //結果:[a, b, c]  
  22.     }  
  23. }  

注意:Class類的getName方法返回值中[表示數組的深度,[表示一維數組,[[表示二維數組。剩下的內容則表示數組類型,例如I表示int類型。

 

7.3  Array類用於完成對數組的反射操作

反射中,Array類表示數組,位於java.lang.reflect 包中。

 

Array類對數組的反射操作:

[java] view plain copy
  1. import java.lang.reflect.Array;  
  2.   
  3. public class ArrayReflect3 {  
  4.     public static void main(String[] args) throws Exception {  
  5.         String[] a1 = new String[]{"a","b","c"};  
  6.         String a2 = "xyz";  
  7.          
  8.         printObject(a1);  
  9.         printObject(a2);  
  10.     }  
  11.     
  12.     public static void printObject(Object obj){  
  13.         Class clazz = obj.getClass(); //獲取Class實例對象  
  14.           
  15.         if(clazz.isArray()){ //如果是數組  
  16.             int len = Array.getLength(obj); //獲取數組長度  
  17.             for(int i = 0; i < len; i++){  
  18.                 System. out.println(Array.get(obj, i)); //打印 obj[i]  
  19.             }  
  20.         }else{  
  21.            System. out.println(obj);  
  22.         }  
  23.    }  
  24. }  


8  反射的作用:實現框架功能

框架:
我做房子賣給用戶住,由用戶自己安裝門窗和空調,我做的房子就是框架,用戶需要使用我的框架,把門窗插入進我提供的框架中。

框架與工具類有區別,工具類被用戶的類調用,而框架則是調用用戶提供的類。

 

框架要解決的核心問題:

我在寫框架(房子)時,你這個用戶可能還在上小學,還不會寫程序呢?我寫的框架程序怎樣能調用到你以後寫的類(門窗)呢?
因爲在寫才程序時無法知道要被調用的類名,所以,在程序中無法直接new 某個類的實例對象了,而要用反射方式來做。

 

下面的代碼中實現一個簡單的框架,使用時,我們只需更改配置文件中的值,配置文件config.properties存放在當前目錄。

Config.properties文件中內容:

className1=java.util.HashSet

className2=java.util.ArrayList

 

代碼及註釋:

[java] view plain copy
  1. package mypkg;  
  2. import java.io.*;  
  3. import java.util.*;  
  4.   
  5. public class FrameReflect{  
  6.     public static void main(String[] args) throws Exception {  
  7.         InputStream is = new FileInputStream("config.properties");  
  8.         Properties props = new Properties();  
  9.         props.load(is);  //加載配置文件中的內容到Properties集合(鍵值對)  
  10.         is.close();  
  11.           
  12.         //config.properties配置文件中內容“className1=java.util.HashSet”  
  13.         String className = (String)props.get("className1");  
  14.         Collection collections = (Collection)Class.forName(className).newInstance();  
  15.           
  16.         ReflectPoint pt1 = new ReflectPoint(33);  
  17.         ReflectPoint pt2 = new ReflectPoint(55);  
  18.         ReflectPoint pt3 = new ReflectPoint(33);  
  19.           
  20.         collections.add(pt1);  
  21.         collections.add(pt2);  
  22.         collections.add(pt3);  
  23.         collections.add(pt1);  
  24.           
  25.         //pt1.y = 7;    
  26.         //collections.remove(pt1); //哈希值改變,無法刪除  
  27.           
  28.         System. out.println(collections.size()); //結果爲2  
  29.           
  30.     /* 當集合爲HashSet時,需要通過比較hashcode值以及equals方法是否返回true決定是否放入。 
  31.     如果hashcode值相等並且equals方法返回true,那麼就不會放入。因此,集合size爲3。 
  32.     如果想讓size爲2,也就是pt1與pt3作爲同一個元素存入HashSet集合, 
  33.     那就需要覆蓋ReflectPoint類的hashCode方法以及equals方法。 */  
  34.     }  
  35. }  
  36. /*  
  37. 注意: 
  38. 當一個對象被存儲進HashSet集合中以後,就不能修改這個對象中的那些參與計算哈希值的字段了, 
  39. 否則,對象修改後的哈希值與最初存儲進HashSet集合中時的哈希值就不同了, 
  40. 在這種情況下,即使在contains方法使用該對象的當前引用作爲的參數去HashSet集合中檢索對象, 
  41. 也將返回找不到對象的結果,這也會導致無法從HashSet集合中單獨刪除當前對象, 
  42. 從而造成內存泄露。 
  43.  */  
  44. class ReflectPoint {  
  45.     private int x ;  
  46.     public int y ;  
  47.       
  48.     public ReflectPoint(int x, int y) {  
  49.          super();  
  50.          this.x = x;  
  51.          this.y = y;  
  52.     }  
  53.     @Override  
  54.     public int hashCode() {  
  55.          final int prime = 31;  
  56.          int result = 1;  
  57.         result = prime * result + x;  
  58.         result = prime * result + y;  
  59.          return result;  
  60.     }  
  61.     @Override  
  62.     public boolean equals(Object obj) {  
  63.          if (this == obj)  
  64.                return true ;  
  65.          if (obj == null)  
  66.                return false ;  
  67.          if (getClass() != obj.getClass())  
  68.                return false ;  
  69.         ReflectPoint other = (ReflectPoint) obj;  
  70.          if (x != other.x)  
  71.                return false ;  
  72.          if (y != other.y)  
  73.                return false ;  
  74.          return true ;  
  75.     }  
  76. }  

運行結果:



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章