1. 程式人生 > 實用技巧 >Java學習_反射

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​(Objectobj) 返回指定物件上此欄位表示的欄位的值。

    • 設定欄位值

      • 設定欄位值是通過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 }