Java反射

http://blog.csdn.net/ichsonx/article/details/9108173

http://blog.csdn.net/liujiahan629629/article/details/18013523

http://blog.csdn.net/shakespeare001/article/details/8082634


TestRef.java
import java.lang.reflect.Method; 
import java.lang.reflect.InvocationTargetException;

/** 
* Created by IntelliJ IDEA. 
* File: TestRef.java 
* User: leizhimin 
* Date: 2008-1-28 14:48:44 
*/
 
public class TestRef {

    public staticvoid main(String args[]) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Foo foo = new Foo("這個一個Foo對象!");
        Class clazz = foo.getClass(); 
        Method m1 = clazz.getDeclaredMethod("outInfo");
        Method m2 = clazz.getDeclaredMethod("setMsg", String.class);
        Method m3 = clazz.getDeclaredMethod("getMsg");
        m1.invoke(foo); 
        m2.invoke(foo, "重新設置msg信息!"); 
        String msg = (String) m3.invoke(foo); 
        System.out.println(msg); 
    } 


class Foo { 
    private String msg; 

    public Foo(String msg) { 
        this.msg = msg; 
    } 

    public void setMsg(String msg) {
        this.msg = msg; 
    } 

    public String getMsg() { 
        return msg; 
    } 

    public void outInfo() {
        System.out.println("這是測試Java反射的測試類"); 
    } 
}
 
控制檯輸出結果:
這是測試Java反射的測試類 
重新設置msg信息! 

Process finished with exit code 0 


 



傳送門:http://blog.csdn.net/hbcui1984/article/details/2719089

JAVA反射使用手

        本篇文章爲在工作中使用JAVA反射的經驗總結,也可以說是一些小技巧,以後學會新的小技巧,會不斷更新。本文不準備討論JAVA反射的機制,網上有很多,大家隨便google一下就可以了。

        在開始之前,我先定義一個測試類Student,代碼如下:

[Java] view plaincopy
  1. package chb.test.reflect;  
  2.   
  3. public class Student {  
  4.     private int age;  
  5.     private String name;  
  6.     public int getAge() {  
  7.         return age;  
  8.     }  
  9.     public void setAge(int age) {  
  10.         this.age = age;  
  11.     }  
  12.     public String getName() {  
  13.         return name;  
  14.     }  
  15.     public void setName(String name) {  
  16.         this.name = name;  
  17.     }  
  18.       
  19.     public static void hi(int age,String name){  
  20.         System.out.println("大家好,我叫"+name+",今年"+age+"歲");  
  21.     }  
  22. }<pre></pre>  

一、JAVA反射的常規使用步驟

    反射調用一般分爲3個步驟:

  • 得到要調用類的class
  • 得到要調用的類中的方法(Method)
  • 方法調用(invoke)

     代碼示例:

[Csharp] view plaincopy
  1. Class cls = Class.forName("chb.test.reflect.Student");  
  2. Method m = cls.getDeclaredMethod("hi",new Class[]{int.class,String.class});  
  3. m.invoke(cls.newInstance(),20,"chb");<pre></pre>  

二、方法調用中的參數類型

        在方法調用中,參數類型必須正確,這裏需要注意的是不能使用包裝類替換基本類型,比如不能使用Integer.class代替int.class。

       如我要調用Student的setAge方法,下面的調用是正確的:

 

[Java] view plaincopy
  1. Class cls = Class.forName("chb.test.reflect.Student");  
  2. Method setMethod = cls.getDeclaredMethod("setAge",int.class);  
  3. setMethod.invoke(cls.newInstance(), 15);<pre></pre>  

 

       而如果我們用Integer.class替代int.class就會出錯,如:

[Java] view plaincopy
  1. Class cls = Class.forName("chb.test.reflect.Student");  
  2. Method setMethod = cls.getDeclaredMethod("setAge",Integer.class);  
  3. setMethod.invoke(cls.newInstance(), 15);<pre></pre>  

 

       jvm會報出如下異常:

[HTML] view plaincopy
  1. java.lang.NoSuchMethodException: chb.test.reflect.Student.setAge(java.lang.Integer)  
  2.     at java.lang.Class.getDeclaredMethod(Unknown Source)  
  3.     at chb.test.reflect.TestClass.testReflect(TestClass.java:23)<pre></pre>  

 

三、static方法的反射調用

 

       static方法調用時,不必得到對象示例,如下:

[Java] view plaincopy
  1. Class cls = Class.forName("chb.test.reflect.Student");  
  2. Method staticMethod = cls.getDeclaredMethod("hi",int.class,String.class);  
  3. staticMethod.invoke(cls,20,"chb");//這裏不需要newInstance  
  4. //staticMethod.invoke(cls.newInstance(),20,"chb");<pre></pre>  

四、private的成員變量賦值

    如果直接通過反射給類的private成員變量賦值,是不允許的,這時我們可以通過setAccessible方法解決。代碼示例:

[Java] view plaincopy
  1. Class cls = Class.forName("chb.test.reflect.Student");  
  2. Object student = cls.newInstance();//得到一個實例  
  3. Field field = cls.getDeclaredField("age");  
  4. field.set(student, 10);  
  5. System.out.println(field.get(student));<pre></pre>  

 

     運行如上代碼,系統會報出如下異常:

[HTML] view plaincopy
  1. java.lang.IllegalAccessException: Class chb.test.reflect.TestClass can not access a member of class chb.test.reflect.Student with modifiers "private"  
  2.     at sun.reflect.Reflection.ensureMemberAccess(Unknown Source)  
  3.     at java.lang.reflect.Field.doSecurityCheck(Unknown Source)  
  4.     at java.lang.reflect.Field.getFieldAccessor(Unknown Source)  
  5.     at java.lang.reflect.Field.set(Unknown Source)  
  6.     at chb.test.reflect.TestClass.testReflect(TestClass.java:20)<pre></pre>  

    解決方法:

[Java] view plaincopy
  1. Class cls = Class.forName("chb.test.reflect.Student");  
  2. Object student = cls.newInstance();  
  3. Field field = cls.getDeclaredField("age");  
  4. field.setAccessible(true);//設置允許訪問  
  5. field.set(student, 10);  
  6. System.out.println(field.get(student));<pre></pre>  

    其實,在某些場合下(類中有get,set方法),可以先反射調用set方法,再反射調用get方法達到如上效果,代碼示例:

[Java] view plaincopy
  1. Class cls = Class.forName("chb.test.reflect.Student");  
  2. Object student = cls.newInstance();  
  3.   
  4. Method setMethod = cls.getDeclaredMethod("setAge",Integer.class);  
  5. setMethod.invoke(student, 15);//調用set方法  
  6.               
  7. Method getMethod = cls.getDeclaredMethod("getAge");  
  8. System.out.println(getMethod.invoke(student));//再調用get方法<pre></pre>  


 反射,當時經常聽他們說,自己也看過一些資料,也可能在設計模式中使用過,但是感覺對它沒有一個較深入的瞭解,這次重新學習了一下,感覺還行吧!


           一,先看一下反射的概念:

              主要是指程序可以訪問,檢測和修改它本身狀態或行爲的一種能力,並能根據自身行爲的狀態和結果,調整或修改應用所描述行爲的狀態和相關的語義。

             反射是java中一種強大的工具,能夠使我們很方便的創建靈活的代碼,這些代碼可以再運行時裝配,無需在組件之間進行源代碼鏈接。但是反射使用不當會成本很高!

             看概念很暈的,繼續往下看。

 

      二,反射機制的作用:

              1,反編譯:.class-->.java

              2,通過反射機制訪問java對象的屬性,方法,構造方法等;

             這樣好像更容易理解一些,下邊我們具體看怎麼實現這些功能。


      三,在這裏先看一下sun爲我們提供了那些反射機制中的類:

java.lang.Class;                

java.lang.reflect.Constructor; java.lang.reflect.Field;        

java.lang.reflect.Method;

java.lang.reflect.Modifier;


            很多反射中的方法,屬性等操作我們可以從這四個類中查詢。還是哪句話要學着不斷的查詢API,那纔是我們最好的老師。


         四,具體功能實現:

                1,反射機制獲取類有三種方法,我們來獲取Employee類型

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. //第一種方式:  
  2. Classc1 = Class.forName("Employee");  
  3. //第二種方式:  
  4. //java中每個類型都有class 屬性.  
  5. Classc2 = Employee.class;  
  6.    
  7. //第三種方式:  
  8. //java語言中任何一個java對象都有getClass 方法  
  9. Employeee = new Employee();  
  10. Classc3 = e.getClass(); //c3是運行時類 (e的運行時類是Employee)  

 

    2,創建對象:獲取類以後我們來創建它的對象,利用newInstance

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Class c =Class.forName("Employee");  
  2.   
  3. //創建此Class 對象所表示的類的一個新實例  
  4. Objecto = c.newInstance(); //調用了Employee的無參數構造方法.  


    3,獲取屬性:分爲所有的屬性和指定的屬性:

      a,先看獲取所有的屬性的寫法:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. //獲取整個類  
  2.             Class c = Class.forName("java.lang.Integer");  
  3.               //獲取所有的屬性?  
  4.             Field[] fs = c.getDeclaredFields();  
  5.        
  6.                    //定義可變長的字符串,用來存儲屬性  
  7.             StringBuffer sb = new StringBuffer();  
  8.             //通過追加的方法,將每個屬性拼接到此字符串中  
  9.             //最外邊的public定義  
  10.             sb.append(Modifier.toString(c.getModifiers()) + " class " + c.getSimpleName() +"{\n");  
  11.             //裏邊的每一個屬性  
  12.             for(Field field:fs){  
  13.                 sb.append("\t");//空格  
  14.                 sb.append(Modifier.toString(field.getModifiers())+" ");//獲得屬性的修飾符,例如public,static等等  
  15.                 sb.append(field.getType().getSimpleName() + " ");//屬性的類型的名字  
  16.                 sb.append(field.getName()+";\n");//屬性的名字+回車  
  17.             }  
  18.       
  19.             sb.append("}");  
  20.       
  21.             System.out.println(sb);  

       b,獲取特定的屬性,對比着傳統的方法來學習:


[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public static void main(String[] args) throws Exception{  
  2.               
  3. <span style="white-space:pre">  </span>//以前的方式:  
  4.     /* 
  5.     User u = new User(); 
  6.     u.age = 12; //set 
  7.     System.out.println(u.age); //get 
  8.     */  
  9.               
  10.     //獲取類  
  11.     Class c = Class.forName("User");  
  12.     //獲取id屬性  
  13.     Field idF = c.getDeclaredField("id");  
  14.     //實例化這個類賦給o  
  15.     Object o = c.newInstance();  
  16.     //打破封裝  
  17.     idF.setAccessible(true); //使用反射機制可以打破封裝性,導致了java對象的屬性不安全。  
  18.     //給o對象的id屬性賦值"110"  
  19.     idF.set(o, "110"); //set  
  20.     //get  
  21.     System.out.println(idF.get(o));  
  22. }  

 4,獲取方法,和構造方法,不再詳細描述,只來看一下關鍵字:

方法關鍵字

含義

getDeclaredMethods()

獲取所有的方法

getReturnType()

獲得方法的放回類型

getParameterTypes()

獲得方法的傳入參數類型

getDeclaredMethod("方法名",參數類型.class,……)

獲得特定的方法

 

 

構造方法關鍵字

含義

getDeclaredConstructors()

獲取所有的構造方法

getDeclaredConstructor(參數類型.class,……)

獲取特定的構造方法

 

 

父類和父接口

含義

getSuperclass()

獲取某類的父類

getInterfaces()

獲取某類實現的接口

  

         這樣我們就可以獲得類的各種內容,進行了反編譯。對於JAVA這種先編譯再運行的語言來說,反射機制可以使代碼更加靈活,更加容易實現面向對象。

 

  五,反射加配置文件,使我們的程序更加靈活:

             在設計模式學習當中,學習抽象工廠的時候就用到了反射來更加方便的讀取數據庫鏈接字符串等,當時不是太理解,就照着抄了。看一下.NET中的反射+配置文件的使用:

             當時用的配置文件是app.config文件,內容是XML格式的,裏邊填寫鏈接數據庫的內容:

[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1.       <configuration>  
  2. lt;appSettings>  
  3. <add     key=""  value=""/>  
  4. lt;/appSettings>  
  5.         </configuration>  

 反射的寫法:   

[csharp] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. assembly.load("當前程序集的名稱").CreateInstance("當前命名空間名稱".要實例化的類名);  

 

          這樣的好處是很容易的方便我們變換數據庫,例如我們將系統的數據庫從SQL Server升級到Oracle,那麼我們寫兩份D層,在配置文件的內容改一下,或者加條件選擇一下即可,帶來了很大的方便。

            

         當然了,JAVA中其實也是一樣,只不過這裏的配置文件爲.properties,稱作屬性文件。通過反射讀取裏邊的內容。這樣代碼是固定的,但是配置文件的內容我們可以改,這樣使我們的代碼靈活了很多!


    綜上爲,JAVA反射的再次學習,靈活的運用它,能夠使我們的代碼更加靈活,但是它也有它的缺點,就是運用它會使我們的軟件的性能降低,複雜度增加,所以還要我們慎重的使用它。



一、什麼是反射機制?爲什麼要用反射機制?

所謂Java反射機制是指,程序在運行狀態時,可以加載一個運行時才得知名稱的class,能夠知道這個類的所有屬性和方法,並生成其對象實體、或對其fields設值、或調用其方法;即利用反射技術,根據一個類名稱,可以得到該類的構造方法、屬性、方法等信息,並創建其對象。用一句話來概括,反射就是加載一個運行時才知道的類以及它的完整內部結構。 

那我們爲什麼要用反射機制呢?

第一,反射的目的就是爲了擴展未知的應用。比如,我們寫好了一個軟件,其中定義了一些接口,程序已經過編譯並且發佈了,當我們以後需要擴展功能時,不可能去修改已經安裝在別人機器上的軟件源碼,此時我們只需要另寫一個插件,讓其實現某些接口即可,程序運行時,通過反射技術動態的創建和編譯新寫的類,並獲知其內部細節,就可以調用其方法了;

第二,在編碼階段不知道那個類名,要在運行期從配置文件讀取類名, 這時候就沒有辦法以new的方式硬編碼,而必須用到反射才能創建這個對象。 

二、如何使用反射?

1. 反射中的類

Java反射機制的實現要藉助於4個類:ClassConstructorFieldMethod,其中,

Class——類對象(Class 類的實例表示正在運行的 Java 應用程序中的類和接口 

Constructor——類的構造器對象

Field——類的屬性對象

Method——類的方法對象

可以看到一個類的各個組成部分(構造方法、屬性、方法)都被封裝成一個單獨的類。

而java.lang.Class 提供了四種獨立的反射調用,以不同的方式來獲得以上信息。

獲取構造方法的反射調用:

Constructor getConstructor(Class[] params) -- 匹配給定的參數類型來得到相應的公共構造函數  

Constructor[] getConstructors() -- 獲得類的所有公共構造函數  

Constructor getDeclaredConstructor(Class[] params) -- 匹配給定的參數類型來得到相應 的構造函數 (從publicprivateprotect、默認中匹配選擇

Constructor[] getDeclaredConstructors() -- 獲得類的所有構造函數

獲取字段屬性的反射調用:

Field getField(String name) -- 獲得匹配給定名稱參數的公共字段  

Field[] getFields() -- 獲得類的所有公共字段  

Field getDeclaredField(String name) -- 獲得匹配給定名稱參數的字段(從publicprivateprotect、默認中匹配選擇  

Field[] getDeclaredFields() -- 獲得類聲明的所有字段 

獲取方法的反射調用:

Method getMethod(String name, Class[] params) -- 匹配給定的方法參數名name和參數 類型param的公共方法  

Method[] getMethods() -- 獲得類的所有公共方法  

Method getDeclaredMethod(String name, Class[] params) -- 匹配給定的方法參數名 name和參數類型param的方法(從publicprivateprotect、默認中匹配選擇

Method[] getDeclaredMethods() -- 獲得類聲明的所有方法

 

2. 反射的使用步驟

步驟一:獲取類的Class對象和創建類的實例對象

· 獲取類的Class對象:

① 通過Class.forName(String className) ——className表示完整的類路徑

② 運用.class語法,如String.class

③ 調用ObjectgetClass()方法,如:

String str = "abc";

Class c = str.getClass();

④ 對於基本數據類型,可以使用其包裝類中與定義好的TYPE字段,如

Class c = Integer.TYPE;

其中,以“.class”來創建Class對象的引用時,不會自動地初始化該Class對象,而使用Class.forName()立即就進行了初始化。例如,如果一個static final屬性值是“編譯期常量”(public static final int A = 2;),那麼這個值不需要對類進行初始化就可以被讀取。

注:獲取Class的引用,其實就是在加載或尋找編譯期編譯出來的“.class”字節碼文件。

附(

Class.forName() 和 ClassLoader.loadClass()的區別

Class.forName("xx")等同於 Class.forName("xx",true,CALLClass.class.getClassLoader()),第二個參數(bool)表示裝載類的時候是否初始化該類,即調用類的靜態塊的語句及初始化靜態成員變量併爲其分配空間
ClassLoader loader = Thread.currentThread.getContextClassLoader(); //也可以用(ClassLoader.getSystemClassLoader())

Class cls = loader.loadClass("xx"); //這句話沒有執行初始化,其實與 Class.forName("xx.xx"false,loader)是一致的,只是loader.loadClass("xx")執行的 是更底層的操作。
只有執行cls.NewInstance()才能夠初始化類,得到該類的一個實例 

· 創建類的實例對象,如:

Class c = Class.forName("com.ReflectionTest.");

Object obj = c.newInstance();

步驟二:調用諸如getDeclaredMethods()的方法,已取得類的構造方法、屬性或者方法

例如:

Class c = Class.forName("java.lang.String");

Method m[] = c.getDeclaredMethods(); 

步驟三:對於第二步驟取得的屬性、方法、構造方法等採用Reflection API進行處理

下面引用一個例子:

 /**
    * 通過java的反射機制動態修改對象的屬性
    * @param o
    */
    public void test2(Customer o) {
        try {
            Class c = o.getClass();
            //getMethod方法第一個參數指定一個需要調用的方法名稱,第二個參數是需要調用方法的參數類型列表,如無參數可以指定null,該方法返回一個方法對象 
            Method sAge = c.getMethod("setAge", new Class[] { int.class });
            Method gAge = c.getMethod("getAge", null);
            Method sName = c.getMethod("setName", new Class[] { String.class });
            //動態修改Customer對象的age
            Object[] args1 = { new Integer(25) };
            sAge.invoke(o, args1);
            //動態取得Customer對象的age
            Integer AGE = (Integer) gAge.invoke(o, null);
            System.out.println("the Customer age is: " + AGE.intValue());
            //動態修改Customer對象的name
            Object[] args2 = { new String("李四") };
            sName.invoke(o, args2);

        } catch (Throwable e) {
            System.err.println(e);
        }
    }

3.反射機制的功能?

① 在運行時查詢並調用任意一個類所具有的成員變量和方法;

② 在運行時判斷任意一個對象所屬的類。例如:

String str = new String("abc");

Class c = str.getClass();

Boolean flag = c.isInstance(new String()) 

③ 在運行時創建類的實例;

·運行時創建實例對象

有兩種辦法生成實例對象:

針對無參數的構造方法,可以使用Class類裏的newInstance()方法,該newInstance()方法不帶參。例如:

Class c = String.class;

Object obj = c.newInstance();

要調用有參數的構造方法,就需要通過Constructor類的newInstance(Object ... obj)方法帶參。例如:

//獲得StringClass實例

Class clazz=String.class;    

//創建一個數組,這個數組用來放要實例化對象的構造器裏的參數類型    

Class[] param=new Class[1];

//放入構造器中的參數類型,如果有多個,按順序放入   

param[0]=String.class;    

//實例化一個構造器對象,並把放着構造器參數類型的數組作爲參數傳進去    

Constructor constructor=clazz.getConstructor(param);

//創建一個Object數組,用於放構造器中對應的值    

Object[] obj=new Object[1];    

//將值放入到數組中,這裏要注意,param數組中放入的是什麼類型,這裏就要按順序放入    

obj[0]="zhang3";    

//實例化對象,並把放着構造器要傳入的參數的數組傳到方法中    

String str=(String)constructor.newInstance(obj)

這樣,我們就通過java.lang.reflect.Constructor實例化出來了String類型的對象。

·newInstance()new關鍵字創建實例的比較

在初始化一個類,生成一個實例的時候,newInstance()方法和new關鍵字除了一個 是方法,一個是關鍵字外,最主要的區別在於創建對象的方式不一樣,前者是使用類加載機制,後者是創建一個新類。那麼爲什麼會有兩種創建對象方式?這主要考慮到軟件的可伸縮、可擴展和可重用等軟件設計思想。

JVM的角度看,我們使用關鍵字new創建一個類的時候,這個類可以沒有被加載。但是使用newInstance()方法的時候,就必須保證:

1、這個類已經加載;

2、這個類已經鏈接了(即爲靜態域分配存儲空間,並且如果必須的話將解析這個類創建的對其他類的所有引用)。而完成上面兩個步驟的正是Class的靜態方法forName()所完成的,這個靜態方法調用了啓動類加載器,即加載javaAPI的那個加載器。

可以看出,newInstance()實際上是把new這個方式分解爲兩步,即首先調用Class加載方法加載某個類,然後實例化。+這樣分步的好處是顯而易見的。我們可以在調用class的靜態加載方法forName時獲得更好的靈活性,提供給了一種降耦的手段

最後用最簡單的描述來區分new關鍵字和newInstance()方法的區別:  newInstance:弱類型。低效率。只能調用無參構造方法

New:強類型。相對高效。能調用任何public構造。

jvm會執行靜態代碼段,只要記住一個概念,靜態代碼是和class綁定的,class裝載成功就表示執行了你的靜態代碼了。而且以後不會再走這段靜態代碼了。

④ 當要調用的一個類的方法爲private時,可以採用反射技術調用;

先根據完整類名加載該類——>利用newInstanceConstructor實例化——>通過getDeclaredMethods()得到私有的方法——>取消Java語言訪問檢查以訪問private方法(即調用setAccessible(true)  ——>最後通過Methodinvoke方法調用該私有方法。

⑤ 利用反射實現類的動態加載(實現java動態綁定的前提

多態是Java面嚮對象語言的一個重要特性,而向上轉型是實現多態的重要一步,對於面向接口或父類的引用,JVM是靠後期綁定(動態綁定)的機制進行對象類型的判定的,即在運行時才能確定調用哪些代碼。而實現動態綁定的基礎便是反射機制的應用,通過反射在運行時根據類名得到該類的具體信息。

三、我們學習過的知識中哪些用了反射機制?

工廠模式、SpringIOC(或DI)、AOP都使用到了java反射機制


發佈了14 篇原創文章 · 獲贊 26 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章