Java動態性之--反射機制


1. 動態語言

程序運行時,可以改變結構或變量類型。典型的語言:

  • Pythonrubyjavascript
  • 如下javascript代碼
function test(){

var s = "var a=3;var b=5;alert(a+b);";

eval(s);

}

 

  • C,C++,java不是動態語言,java可以稱之為“准動態語言”。但是java有一定的動態性,我們可以利用反射機制、字節碼操作獲得類似動態語言的特性
  • java的動態性讓編程的時候更加靈活!

 

2. 反射機制reflection

  • 指的是可以於運行時加載、探知、使用編譯其完全未知的類。
  • 程序在運行狀態中,可以動態加載一個只有名稱的類,對於任意一個已加載的類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;

    Class c = Class.forName(cn.stu.test.User);

  • 加載完類之后,在堆內存中,就產生了一個Class類型的對象(一個類只有一個Class對象),這個對象就包含了完整的類的結構信息,我們可以通過這個對象看到類的結構。這個對象就像一面鏡子,透過這個鏡子看到類的結構,所有,我們形象的稱之為:反射

 例1:

 1 public class Demo01 {
 2     public static void main(String[] args) {
 3         String path = "cn.stu.ref.bean.User";
 4         
 5         try {
 6             //方法一
 7             Class clazz = Class.forName(path);
 8             //
 9             /**
10              * ==================================
11              * 對象時表示或封裝一些數據。一個類被加載后,
12              * JVM會創建一個對應該類的Class對象
13              * 類的整個結構信息會會放到對應的Class對象中。
14              * 這個Class對象就是一面鏡子一樣,
15              * 通過這面鏡子我們可以看到對應類的全部信息
16              * ===================================
17              */
18             System.out.println(clazz.hashCode());
19             //一個類只對應一個Class對象
20             Class clazz2 = Class.forName(path);
21             System.out.println(clazz2.hashCode());
22             
23             //方法二
24             Class strClass = String.class;
25             Class strClass2 = path.getClass();
26             System.out.println(strClass==strClass2);
27             
28         } catch (ClassNotFoundException e) {
29             e.printStackTrace();
30         }
31     }
32 }

 

3. Class類介紹

當一個class被加載,或當加載器(class loader)的defineClass()被JVM調用,JVM便自動產生一個Class對象

Class類似Reflection的根源

針對任何你想動態加載、運行的類,唯有先獲得相應的Class對象

 

Class類的對象如何獲取?

  • 運用getClass()
  • 運用Class.forName()  --常用
  • 運用.class語法

 

例2:

 1 public class Demo02 {
 2     
 3     public static void main(String[] args) {
 4         String path = "cn.stu.ref.bean.User";
 5         try {
 6             Class clazz = Class.forName(path);
 7             
 8             //獲取類的名稱
 9             System.out.println(clazz.getName());//類名+包名
10             System.out.println(clazz.getSimpleName());//類名
11             
12             //獲取屬性名
13             Field[] fields = clazz.getFields(); //只能獲得public的屬性
14             Field[] fields2 = clazz.getDeclaredFields();//獲得所有屬性
15             Field field = clazz.getDeclaredField("name");//獲取指定name屬性
16             
17             //獲取方法名稱
18             Method[] method = clazz.getDeclaredMethods();
19             Method m1 = clazz.getDeclaredMethod("getName", null);
20             //如果方法有參,則必須傳遞參數類型對應的class對象
21             Method m2 = clazz.getDeclaredMethod("setName", String.class);
22             
23             //獲取構造器
24             Constructor[] constructors = clazz.getDeclaredConstructors();
25             Constructor c = clazz.getDeclaredConstructor(int.class,int.class,String.class);
26             
27         } catch (Exception e) {
28             // TODO Auto-generated catch block
29             e.printStackTrace();
30         } 
31     }
32 }

 

 例3:

 1 public class Demo03 {
 2     public static void main(String[] args) {
 3         String path = "cn.stu.ref.bean.User";
 4         
 5         try {
 6             Class clazz = Class.forName(path);
 7             
 8             //通過反射API調用構造方法,構造對象
 9             User u = (User) clazz.newInstance();//其實是調用了User的無參構造方法
10             
11             Constructor c = clazz.getDeclaredConstructor(int.class,int.class,String.class);
12             User u2 = (User) c.newInstance(1001,18,"張三");
13             
14             //通過反射API調用普通方法
15             User u3 = (User) clazz.newInstance();
16             Method method = clazz.getDeclaredMethod("setName", String.class);
17             method.invoke(u3,"李四"); //u3.setName("李四")
18             
19             //通過反射API操作屬性
20             User u4 = (User) clazz.newInstance();
21             Field f = clazz.getDeclaredField("name");
22             f.setAccessible(true);//不做安全檢查,直接訪問 
23             f.set(u4, "二麻子"); //通過反射直接寫屬性
24             String name = u4.getName();
25             String name2 = (String) f.get(u4); //通過反射直接讀屬性的值
26         } catch (Exception e) {
27             e.printStackTrace();
28         }
29     }
30 }

 

4. 反射機制性能問題

  • setAccessible
    • 啟動和金庸訪問安全檢查的開關,值為true 則指示反射的對象在使用時應該取消java語言訪問檢查,值為false 則指示反射的對象應該實施java語言訪問檢查。並不是true就能訪問,false就不能訪問。
    • 禁止安全檢查,可以提高反射的運行速度
  • 可以考慮使用: cglib/javaassist字節碼操作

 例4:

 1 /**
 2  * 通過跳過安全檢查,提高反射效率
 3  * 三種執行方法的效率差異比較
 4  */
 5 public class Demo04 {
 6     
 7     public static void test01(){
 8         User u = new User();
 9         
10         long startTime = System.currentTimeMillis();
11         
12         for (int i = 0; i < 1000000000L; i++) {
13             u.getName();
14         }
15         
16         long endTime = System.currentTimeMillis();
17         System.out.println("普通方法調用,執行10億次,耗時:"+(endTime-startTime)+"ms"); 
18     }
19     
20     public static void test02() throws Exception{
21         User u = new User();
22         Class clazz = u.getClass();
23         Method m = clazz.getDeclaredMethod("getName", null);
24 //        m.setAccessible(true);
25         
26         long startTime = System.currentTimeMillis();
27         
28         for (int i = 0; i < 1000000000L; i++) {
29             m.invoke(u, null);
30         }
31         
32         long endTime = System.currentTimeMillis();
33         System.out.println("反射動態方法調用,執行10億次,耗時:"+(endTime-startTime)+"ms");
34     }
35     
36     public static void test03() throws Exception{
37         User u = new User();
38         Class clazz = u.getClass();
39         Method m = clazz.getDeclaredMethod("getName", null);
40         m.setAccessible(true);    //不需要執行訪問安全檢查
41         
42         long startTime = System.currentTimeMillis();
43         
44         for (int i = 0; i < 1000000000L; i++) {
45             m.invoke(u, null);
46         }
47         
48         long endTime = System.currentTimeMillis();
49         System.out.println("反射動態方法調用,跳過安全檢查,執行10億次,耗時:"+(endTime-startTime)+"ms");
50     }
51     
52     
53     public static void main(String[] args) throws Exception {
54         test01();
55         test02();
56         test03();
57     }
58 }

輸出結果:

    普通方法調用,執行10億次,耗時:6665ms
    反射動態方法調用,執行10億次,耗時:62947ms
    反射動態方法調用,跳過安全檢查,執行10億次,耗時:11864ms

 

 

5. 反射操作泛型

  • java采用泛型擦除的機制來引入泛型。java中的泛型僅僅是給編譯器javac使用的,確保數據的安全性和免去強制類型轉換的麻煩,但是,一旦編譯完成,所有的和泛型有關的類型全部擦除。
  • 為了通過反射操操作這些類型以迎合實際開發的需要,java就新增了ParameterzedTypeGenericArrayTypeTypeVariableWildcardType幾種類型來代表不能被詭異到Class類中的類型但是又和原始類型齊名的類型。

 

  • ParmterizedType: 表示一種參數化的類型,比如Collection<String>
  • GenericArrayType: 表示一種元素類型是參數化類型或者類型變量的數組類型
  • TypeVariable:是各種類型變量的公共父接口
  • WildcardType:代表一種通配符類型表達式


免責聲明!

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



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