Java反射機制


一、什么是反射?

在運行狀態中,對於任意一個類,都能夠獲取到這個類的所有屬性和方法,對於任意一個對象,都能夠調用它的任意一個方法和屬性(包括私有的方法和屬性),這種動態獲取的信息以及動態調用對象的方法的功能就稱為java語言的反射機制。通俗點講,通過反射,該類對我們來說是完全透明的,想要獲取任何東西都可以。
想要使用反射機制,就必須要先獲取到該類的字節碼文件對象(.class),通過字節碼文件對象,就能夠通過該類中的方法獲取到我們想要的所有信息(方法,屬性,類名,父類名,實現的所有接口等等),每一個類對應着一個字節碼文件也就對應着一個Class類型的對象,也就是字節碼文件對象。
獲取字節碼文件對象的三種方式。

1、第一種方式-->Class.forName("類名字符串");
  //通過Class類中的靜態方法forName,直接獲取到一個類的字節碼文件對象,此時該類還是源文件階段,並沒有變為字節碼文件。
  Class c1=Class.forName("csdn.Student");
2、第二種方式-->先創建對象,再用對象調用getClass()方法,即實例對象.getClass().返回運行時類。
  //通過類的實例獲取該類的字節碼文件對象,該類處於創建對象階段,任何一個java對象都有getClass()方法
  Student s=new Student();
  Class c2 = s.getClass();
3、第三種方式-->類名.class。返回Class的對象。(每個類都有class屬性)
  //當類被加載成.class文件時,此時Person類變成了.class,在獲取該字節碼文件對象,也就是獲取自己, 該類處於字節碼階段。
  Class c3=Student.class;

 有了字節碼文件對象才能獲得類中所有的信息,我們在使用反射獲取信息時,也要考慮使用上面哪種方式獲取字節碼對象合理,視不同情況而定。下面介紹Class類的功能。

二、反射機制能夠獲取哪些信息?Class類的API詳解。

2.1、通過字節碼對象創建實例對象

//注:類名字符串是"包名+類名" 返回Class的對象。(這種是最常用的方法)
Class c1=Class.forName("csdn.Student"); // 創建student實例,通過student的無參構造方法
Student stu = (Student)c1.newInstance();

2.2、獲取指定構造器方法。constructor 如果沒有無參構造,只有有參構造如何創建實例呢?看下面

 

1 //獲取字節碼文件
2 Class clazz1=Class.forName("csdn.Student"); 3 //先獲取有參構造器,parameterTypes;表示參數列表,有多少寫多少,也可以不寫,不寫就是調用無參構造器
4 Constructor constructor = clazz1.getConstructor(int.class,String.class); 5 //通過構造器來實例化對象,將實際的參數傳進去
6 Student student = (Student)constructor.newInstance("小明",12);

  總結上面創建實例對象:Class類的newInstance()方法是使用該類無參的構造函數創建對象, 如果一個類沒有無參的構造函數, 就不能這樣創建了,可以調用Class類的getConstructor(String.class,int.class)方法獲取一個指定的構造函數然后再調用Constructor類的newInstance("張三",20)方法創建對象

獲取全部構造方法

        Class clazz1 = Class.forName("Reflect.Student"); //獲取所有的構造方法
        Constructor[] constructors = clazz1.getConstructors(); //遍歷所有的構造方法
        for(int i= 0;i<constructors.length;i++) { //獲取每個構造函數中的參數類型字節碼
            Class[] parameterTypes = constructors[i].getParameterTypes(); //獲取構造函數中參數類型
            System.out.println("第"+i+"個構造函數"); for(int j=0;j<parameterTypes.length;j++) { //獲取構造函數中參數類型
                System.out.println(parameterTypes[j].getName()+","); } } 

2.3、獲取成員變量並使用  Field對象

  

獲取指定成員變量

Class clazz1 = Class.forName("Reflect.Student"); //獲取其實例對象
Student student = (Student)clazz1.newInstance(); //獲取成員變量clazz1.getField(name);通過name來獲取指定成員變量,如果該成員變量是私有的,則應該使用getDeclaredField(name);
Field field = clazz1.getDeclaredField("id"); //對其成員變量進行操作 //賦值操作
field.setInt(student, 1); //獲取成員變量的值,field.get(obj);obj為所表示字段的值的對象,也就是該屬性對應誒的實例對象。
System.out.println(field.getInt(student));

  Class.getField(String)方法可以獲取類中的指定字段(可見的), 如果是私有的可以用getDeclaedField("name")方法獲取,通過set(obj, "李四")方法可以設置指定對象上該字段的值, 如果是私有的需要先調用setAccessible(true)設置訪問權限,用獲取的指定的字段調用get(obj)可以獲取指定對象中該字段的值.

獲取全部成員變量

        // 通過反射獲得字節碼文件對象
        Class clazz1 = Class.forName("Reflect.Student"); // 實例化
        Student student = (Student) clazz1.newInstance(); // 賦值操作
        student.setId(3); student.setName("zhangsan"); // 將私有的屬性一並獲取
        Field[] fields = clazz1.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { // 打開操作權限
            fields[i].setAccessible(true); // 獲取成員變量的值
 System.out.println(fields[i].get(student)); } 

2.4、獲得方法並使用  Method

 

         // 通過反射獲得字節碼文件對象
        Class clazz1 = Class.forName("Reflect.Student"); // 實例化
        Student student = (Student) clazz1.newInstance(); /** * clazz1.clazz1.getMethod(name, parameterTypes) * name 方法的名字 * parameterTypes:方法的參數類型為Class類型,沒有則不填寫,比如參數為String,則填寫為String.Class */
        //eat為不帶參數的public方法
        Method method = clazz1.getMethod("eat"); Method method1 = clazz1.getMethod("sing", String.class); /** * method.invoke(obj, args) * obj:方法的對象 * args:是的參數值,沒有則不填 */
        //調用方法
 method.invoke(student); method1.invoke(student, "呵呵"); //獲取私有的方法,和獲取屬性一樣,say為私有的方法
        Method method2 = clazz1.getDeclaredMethod("say"); method2.setAccessible(true); method2.invoke(student);

  Class.getMethod(String, Class...) 和 Class.getDeclaredMethod(String, Class...)方法可以獲取類中的指定方法,如果為私有方法,則需要打開一個權限。setAccessible(true);用invoke(Object, Object...)可以調用該方法,
跟上面同理,也能一次性獲得所有的方法

     // 通過反射獲得字節碼文件對象
         Class clazz1 = Class.forName("Reflect.Student"); Method[] methods = clazz1.getDeclaredMethods(); // 實例化
        Student student = (Student) clazz1.newInstance(); for(Method method :methods) { method.setAccessible(true); System.out.println(method.getName()); //獲得方法的參數,又回到了之前的代碼
            Class<?>[] parameterTypes = method.getParameterTypes(); for(int i=0;i<parameterTypes.length;i++) { //獲取構造函數中參數類型
                System.out.println(parameterTypes[i].getName()+","); } }

2.5、獲得該類的所有接口

  Class[] getInterfaces():確定此對象所表示的類或接口實現的接口
  返回值:接口的字節碼文件對象的數組

2.6、獲取指定資源的輸入流

    InputStream getResourceAsStream(String name)  
    return:一個 InputStream 對象;如果找不到帶有該名稱的資源,則返回 null
    參數:所需資源的名稱,如果以"/"開始,則絕對資源名為"/"后面的一部分。

  2.7、動態代理的概述和實現

    動態代理:一種設計模式,其非常簡單,很容易理解,你自己可以做這件事,但是覺得自己做非常麻煩或者不方便,所以就叫一個另一個人(代理)來幫你做這個事情,而你就不用管了,這就是動態代理。舉個例子,買火車票叫人代買。
    在程序運行過程中產生的這個對象,而程序運行過程中產生對象其實就是我們剛才反射講解的內容,所以,動態代理其實就是通過反射來生成一個代理
    在Java中java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler接口,通過使用這個類和接口就可以生成動態代理對象。JDK提供的代理只能針對接口做代理。我們有更強大的代理cglib,Proxy類中的方法創建動態代理類對象 
    分三步,但是注意JDK提供的代理正能針對接口做代理,也就是下面的第二步返回的必須要是一個接口。
       1、new出代理對象,通過實現InvacationHandler接口,然后new出代理對象來。
       2、通過Proxy類中的靜態方法newProxyInstance,來將代理對象假裝成那個被代理的對象,也就是如果叫人幫我們代買火車票一樣,那個代理就假裝成我們自己本人
       3、執行方法,代理成功
   將代理對象中的內容進行實現

 1、2、3步

          

注意newProxyInstance的三個參數,第一個,類加載器,第二個被代理對象的接口,第三個代理對象。

2.8、還有很多方法,比如獲得類加載器,等等

具體還需要別的,就通過查看API文檔來解決。

三、反射機制的應用實例

  3.1、利用反射,在泛型為int的arryaList集合中存放一個String類型的對象

  原理:集合中的泛型只在編譯器有效,而到了運行期,泛型則會失效,

3.2、利用反射,簡化編寫Servlet的個數。

  什么意思呢?每當我們寫一個功能時,就需要寫一個對應的Servlet,導致最后Servlet有很多,自己都看不過來,所以對其進行了優化,兩種方式,
  3.2.1、每次從頁面傳過來一個參數,method="xxx"; 然后編寫一個Servlet,獲得其參數method的值,進行判斷,如果是add,則調用add方法,如果是delete,則調用delete方法,這樣就可以寫在一個servlet中實現所有的功能了。

3.2.2、利用反射
編寫一個BaseServlet繼承HttpServlet,這是一個通用的BaseServlet。需要明白servlet的生命周期

編寫具體實現的方法servlet類。
MySerlvet001 extends BaseServlet

  解釋:需要明白servlet的生命周期,也就是service方法,因為是servlet,所以在訪問的時候,會經過service方法,而子類MyServlet001中並沒有,所以就到父類BaseServlet中找,發現有,然后獲取參數即知道了需要調用什么方法,因為方法的編寫都在子類中,所以通過反射,獲取到子類中對應的方法並運行,其中需要注意的是this這個參數在BaseServlet中的用法。需要理解它。才能理解我們這個程序。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM