Java學習_反射

  • 什麼是反射?
    • 反射就是Reflection,Java的反射是指程式在運行期可以拿到一個對象的所有資訊。
    • 反射是為了解決在運行期,對某個實例一無所知的情況下,如何調用其方法。
    • JAVA反射機制是在運行狀態中,對於任何一個類,都能夠知道這個類的所有屬性和方法;對於任何一個對象,都能夠調用它的任意方法和屬性;這種動態獲取資訊以及動態調用對象方法的功能稱為java語言的反射機制。
  • Class類
    • class是由JVM在執行過程中動態載入的。JVM在第一次讀取到一種class類型時,將其載入進記憶體。每載入一種class,JVM就為其創建一個Class類型的實例,並關聯起來。注意:這裡的Class類型是一個名叫Classclass
      public final class Class {
          private Class() {}
      }

      String類為例,當JVM載入String類時,它首先讀取String.class文件到記憶體,然後,為String類創建一個Class實例並關聯起來。

      Class cls = new Class(String);

      查看JDK源碼,可以發現Class類的構造方法是private,只有JVM能創建Class實例,我們自己的Java程式是無法創建Class實例的。JVM持有的每個Class實例都指向一個數據類型(classinterface)。一個Class實例包含了該class的所有完整資訊。

      ┌───────────────────────────┐
      │      Class Instance       │──────> String
      ├───────────────────────────┤
      │name = "java.lang.String"  │
      └───────────────────────────┘
      ┌───────────────────────────┐
      │      Class Instance       │──────> Random
      ├───────────────────────────┤
      │name = "java.util.Random"  │
      └───────────────────────────┘
      ┌───────────────────────────┐
      │      Class Instance       │──────> Runnable
      ├───────────────────────────┤
      │name = "java.lang.Runnable"│
      └───────────────────────────┘
      ┌───────────────────────────┐
      │      Class Instance       │──────> String
      ├───────────────────────────┤
      │name = "java.lang.String"  │
      ├───────────────────────────┤
      │package = "java.lang"      │
      ├───────────────────────────┤
      │super = "java.lang.Object" │
      ├───────────────────────────┤
      │interface = CharSequence...│
      ├───────────────────────────┤
      │field = value[],hash,...   │
      ├───────────────────────────┤
      │method = indexOf()...      │
      └───────────────────────────┘
    • JVM為每個載入的class創建了對應的Class實例,並在實例中保存了該class的所有資訊,包括類名、包名、父類、實現的介面、所有方法、欄位等,因此,如果獲取了某個Class實例,我們就可以通過這個Class實例獲取到該實例對應的class的所有資訊。這種通過Class實例獲取class資訊的方法稱為反射(Reflection)。

    • 獲取一個classClass實例,有三個方法。
      • 方法一:直接通過一個class的靜態變數class獲取。
        Class cls = String.class;
      • 如果有一個實例變數,可以通過該實例變數提供的getClass()方法獲取。

        String s = "Hello";
        Class cls = s.getClass();
      • 如果知道一個class的完整類名,可以通過靜態方法Class.forName()獲取。

        Class cls = Class.forName("java.lang.String");
    • Class實例在JVM中是唯一的,所以,上述方法獲取的Class實例是同一個實例。可以用==比較兩個Class實例。
      Integer n = new Integer(123);
      
      boolean b1 = n instanceof Integer; // true,因為n是Integer類型
      boolean b2 = n instanceof Number; // true,因為n是Number類型的子類
      
      boolean b3 = n.getClass() == Integer.class; // true,因為n.getClass()返回Integer.class
      boolean b4 = n.getClass() == Number.class; // false,因為Integer.class!=Number.class

      instanceof不但匹配指定類型,還匹配指定類型的子類。而用==判斷class實例可以精確地判斷數據類型,但不能作子類型比較。通常情況下,我們應該用instanceof判斷數據類型,因為面向抽象編程的時候,我們不關心具體的子類型。只有在需要精確判斷一個類型是不是某個class的時候,我們才使用==判斷class實例。

    • Class實例獲取獲取的基本資訊。
       1 public class Main {
       2     public static void main(String[] args) {
       3         printClassInfo("".getClass());
       4         printClassInfo(Runnable.class);
       5         printClassInfo(java.time.Month.class);
       6         printClassInfo(String[].class);
       7         printClassInfo(int.class);
       8     }
       9 
      10     static void printClassInfo(Class cls) {
      11         System.out.println("Class name: " + cls.getName());
      12         System.out.println("Simple name: " + cls.getSimpleName());
      13         if (cls.getPackage() != null) {
      14             System.out.println("Package name: " + cls.getPackage().getName());
      15         }
      16         System.out.println("is interface: " + cls.isInterface());
      17         System.out.println("is enum: " + cls.isEnum());
      18         System.out.println("is array: " + cls.isArray());
      19         System.out.println("is primitive: " + cls.isPrimitive());
      20     }
      21 }
    • 獲取到了一個Class實例,可以通過該Class實例來創建對應類型的實例。
      // 獲取String的Class實例:
      Class cls = String.class;
      // 創建一個String實例:
      String s = (String) cls.newInstance();

      上述程式碼相當於new String()。通過Class.newInstance()可以創建類實例,它的局限是:只能調用public的無參數構造方法。帶參數的構造方法,或者非public的構造方法都無法通過Class.newInstance()被調用。

    • 動態載入

      • JVM在執行Java程式的時候,並不是一次性把所有用到的class全部載入到記憶體,而是第一次需要用到class時才載入。 

  • 訪問欄位
    • Class類提供了以下幾個方法來獲取欄位:

      • Field getField(name):根據欄位名獲取某個public的field(包括父類)
      • Field getDeclaredField(name):根據欄位名獲取當前類的某個field(不包括父類)
      • Field[] getFields():獲取所有public的field(包括父類)
      • Field[] getDeclaredFields():獲取當前類的所有field(不包括父類)
         1 public class Main {
         2     public static void main(String[] args) throws Exception {
         3         Class stdClass = Student.class;
         4         // 獲取public欄位"score":
         5         System.out.println(stdClass.getField("score"));
         6         // 獲取繼承的public欄位"name":
         7         System.out.println(stdClass.getField("name"));
         8         // 獲取private欄位"grade":
         9         System.out.println(stdClass.getDeclaredField("grade"));
        10     }
        11 }
        12 
        13 class Student extends Person {
        14     public int score;
        15     private int grade;
        16 }
        17 
        18 class Person {
        19     public String name;
        20 }
    • 一個Field對象包含了一個欄位的所有資訊:

      • getName():返回欄位名稱,例如,"name"
      • getType():返回欄位類型,也是一個Class實例,例如,String.class
      • getModifiers():返回欄位的修飾符,它是一個int,不同的bit表示不同的含義。
      • String類的value欄位為例,它的定義是:

        public final class String {
            private final byte[] value;
        }

        用反射獲取該欄位的資訊:

        Field f = String.class.getDeclaredField("value");
        f.getName(); // "value"
        f.getType(); // class [B 表示byte[]類型
        int m = f.getModifiers();
        Modifier.isFinal(m); // true
        Modifier.isPublic(m); // false
        Modifier.isProtected(m); // false
        Modifier.isPrivate(m); // true
        Modifier.isStatic(m); // false
    • 獲取欄位值

      • 利用反射拿到欄位的一個Field實例只是第一步,還可以拿到一個實例對應的該欄位的值。例如,對於一個Person實例,可以先拿到name欄位對應的Field,再獲取這個實例的name欄位的值。

         1 import java.lang.reflect.Field;
         2 
         3 public class Main {
         4 
         5     public static void main(String[] args) throws Exception {
         6         Object p = new Person("Xiao Ming");
         7         Class c = p.getClass();
         8         Field f = c.getDeclaredField("name");
        f.setAccessible(true);
        9 Object value = f.get(p); 10 System.out.println(value); // "Xiao Ming" 11 } 12 } 13 14 class Person { 15 private String name; 16 17 public Person(String name) { 18 this.name = name; 19 } 20 }
        Object get​(Object obj)
         
         
        返回指定對象上此 欄位表示的欄位的值。

         

    • 設置欄位值

      • 設置欄位值是通過Field.set(Object, Object)實現的,其中第一個Object參數是指定的實例,第二個Object參數是待修改的值。
         1 import java.lang.reflect.Field;
         2 
         3 public class Main {
         4 
         5     public static void main(String[] args) throws Exception {
         6         Person p = new Person("Xiao Ming");
         7         System.out.println(p.getName()); // "Xiao Ming"
         8         Class c = p.getClass();
         9         Field f = c.getDeclaredField("name");
        10         f.setAccessible(true);
        11         f.set(p, "Xiao Hong");
        12         System.out.println(p.getName()); // "Xiao Hong"
        13     }
        14 }
        15 
        16 class Person {
        17     private String name;
        18 
        19     public Person(String name) {
        20         this.name = name;
        21     }
        22 
        23     public String getName() {
        24         return this.name;
        25     }
        26 }
    • Java的反射API提供的Field類封裝了欄位的所有資訊:
      • 通過Class實例的方法可以獲取Field實例:getField()getFields()getDeclaredField()getDeclaredFields()
      • 通過Field實例可以獲取欄位資訊:getName()getType()getModifiers()
      • 通過Field實例可以讀取或設置某個對象的欄位,如果存在訪問限制,要首先調用setAccessible(true)來訪問非public欄位。
      • 通過反射讀寫欄位是一種非常規方法,它會破壞對象的封裝。
  • 調用方法
    • Class類提供了以下幾個方法來獲取Method

      • Method getMethod(name, Class...):獲取某個publicMethod(包括父類)
      • Method getDeclaredMethod(name, Class...):獲取當前類的某個Method(不包括父類)
      • Method[] getMethods():獲取所有publicMethod(包括父類)
      • Method[] getDeclaredMethods():獲取當前類的所有Method(不包括父類)
         1 public class Main {
         2     public static void main(String[] args) throws Exception {
         3         Class stdClass = Student.class;
         4         // 獲取public方法getScore,參數為String:
         5         System.out.println(stdClass.getMethod("getScore", String.class));
         6         // 獲取繼承的public方法getName,無參數:
         7         System.out.println(stdClass.getMethod("getName"));
         8         // 獲取private方法getGrade,參數為int:
         9         System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
        10     }
        11 }
        12 
        13 class Student extends Person {
        14     public int getScore(String type) {
        15         return 99;
        16     }
        17     private int getGrade(int year) {
        18         return 1;
        19     }
        20 }
        21 
        22 class Person {
        23     public String getName() {
        24         return "Person";
        25     }
        26 }
      • 一個Method對象包含一個方法的所有資訊:

        • getName():返回方法名稱,例如:"getScore"
        • getReturnType():返回方法返回值類型,也是一個Class實例,例如:String.class
        • getParameterTypes():返回方法的參數類型,是一個Class數組,例如:{String.class, int.class}
        • getModifiers():返回方法的修飾符,它是一個int,不同的bit表示不同的含義。
      • 如果用反射來調用substring方法。
         1 import java.lang.reflect.Method;
         2 
         3 public class Main {
         4     public static void main(String[] args) throws Exception {
         5         // String對象:
         6         String s = "Hello world";
         7         // 獲取String substring(int)方法,參數為int:
         8         Method m = String.class.getMethod("substring", int.class);
         9         // 在s對象上調用該方法並獲取結果:
        10         String r = (String) m.invoke(s, 6);
        11         // 列印調用結果:
        12         System.out.println(r);
        13     }
        14 }

         

          

Tags: