day12 12、反射機制淺學

12、反射機制

12.1 什麼是反射機制

反射機制是 Java 語言的一個重要特性。在學習 Java 反射機制前,大家應該先了解兩個概念,編譯期和運行期。

編譯期
是指把源碼交給編譯器編譯成計算機可以執行的文件的過程。在 Java 中也就是把 Java 代碼編成 class 文件的過程。編譯期只是做了一些翻譯功能,並沒有把代碼放在內存中運行起來,而只是把代碼當成文本進行操作,比如檢查錯誤。

運行期
是把編譯後的文件交給計算機執行,直到程序運行結束。所謂運行期就把在磁盤中的代碼放到內存中執行起來。

Java 反射機制
在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能稱爲 Java 語言的反射機制。簡單來說,反射機制指的是程序在運行時能夠獲取自身的信息。在 Java 中,只要給定類的名字,就可以通過反射機制來獲得類的所有信息。
應用
Java 反射機制在服務器程序和中間件程序中得到了廣泛運用。在服務器端,往往需要根據客戶的請求,動態調用某一個對象的特定方法。此外,在 ORM 中間件的實現中,運用 Java 反射機制可以讀取任意一個 JavaBean 的所有屬性,或者給這些屬性賦值。
在這裏插入圖片描述
Java 反射機制主要提供了以下功能,這些功能都位於java.lang.reflect包。

  • 在運行時判斷任意一個對象所屬的類。
  • 在運行時構造任意一個類的對象。
  • 在運行時判斷任意一個類所具有的成員變量和方法。
  • 在運行時調用任意一個對象的方法。
  • 生成動態代理。

要想知道一個類的屬性和方法,必須先獲取到該類的字節碼文件對象。獲取類的信息時,使用的就是 Class 類中的方法。所以先要獲取到每一個字節碼文件(.class)對應的 Class 類型的對象.

衆所周知,所有 Java 類均繼承了 Object 類,在 Object 類中定義了一個 getClass() 方法,該方法返回同一個類型爲 Class 的對象。例如,下面的示例代碼:

Class labelCls = label1.getClass();    // label1爲 JLabel 類的對象

利用 Class 類的對象 labelCls 可以訪問 labelCls 對象的描述信息、JLabel 類的信息以及基類 Object 的信息。表 1 列出了通過反射可以訪問的信息。

表 1 反射可訪問的常用信息
類型 訪問方法 返回值類型 說明
包路徑 getPackage() Package 對象 獲取該類的存放路徑
類名稱 getName() String 對象 獲取該類的名稱
繼承類 getSuperclass() Class 對象 獲取該類繼承的類
實現接口 getlnterfaces() Class 型數組 獲取該類實現的所有接口
構造方法 getConstructors() Constructor 型數組 獲取所有權限爲 public 的構造方法
getDeclared Contruectors() Constructor 對象 獲取當前對象的所有構造方法
方法 getMethods() Methods 型數組 獲取所有權限爲 public 的方法
方法 getDeclaredMethods() Methods 對象 獲取當前對象的所有方法
成員變量 getFields() Field 型數組 獲取所有權限爲 public 的成員變量
成員變量 getDeclareFileds() Field 對象 獲取當前對象的所有成員變量
內部類 getClasses() Class 型數組 獲取所有權限爲 public 的內部類
內部類 getDeclaredClasses() Class 型數組 獲取所有內部類
內部類的聲明類 getDeclaringClass() Class 對象 如果該類爲內部類,則返回它的成員類,否則返回 null

在調用 getFields() 和 getMethods() 方法時將會依次獲取權限爲 public 的字段和變量,包含從超類中繼承到的成員變量和方法。通過 getDeclareFields() 和 getDeclareMethod() 只是獲取在本類中定義的成員變量和方法。

12.2 Java 反射機制的優缺點

優點

  • 能夠運行時動態獲取類的實例,大大提高系統的靈活性和擴展性。
  • 與 Java 動態編譯相結合,可以實現無比強大的功能。
  • 對於 Java 這種先編譯再運行的語言,能夠讓我們很方便的創建靈活的代碼,這些代碼可以在運行時裝配,無需在組件之間進行源代碼的鏈接,更加容易實現面向對象。

缺點

  • 反射會消耗一定的系統資源,因此,如果不需要動態地創建一個對象,那麼就不需要用反射;
  • 反射調用方法時可以忽略權限檢查,獲取這個類的私有方法和屬性,因此可能會破壞類的封裝性而導致安全問題。
  • Java 反射機制在一般的 Java 應用開發中很少使用,即便是 Java EE 階段也很少使用。

12.3 反射機制常見API

實現 Java 反射機制的類都位於 java.lang.reflect 包中,java.lang.Class 類是 Java 反射機制 API 中的核心類。將從這兩個方面講解 Java 反射機制 API。

12.3.1 java.lang.Class 類(實現反射的關鍵所在)

java.lang.Class 類是實現反射的關鍵所在,Class 類的一個實例表示 Java 的一種數據類型,包括類、接口、枚舉、註解(Annotation)、數組、基本數據類型和 void。Class 沒有公有的構造方法,Class 實例是由 JVM 在類加載時自動創建的。

在程序代碼中獲得 Class 實例可以通過如下代碼實現:

// 1. 通過類型class靜態變量
Class clz1 = String.class;//靜態變量
String str = "Hello";
// 2. 通過對象的getClass()方法
Class clz2 = str.getClass();

每一種類型包括類和接口等,都有一個 class 靜態變量可以獲得 Class 實例。另外,每一個對象都有 getClass() 方法可以獲得 Class 實例,該方法是由 Object 類提供的實例方法。

Class 類提供了很多方法可以獲得運行時對象的相關信息,下面的程序代碼展示了其中一些方法。

public class ReflectionTest01 {
    public static void main(String[] args) {
        // 獲得Class實例
        // 1.通過類型class靜態變量
        Class clz1 = String.class;
        String str = "Hello";
        // 2.通過對象的getClass()方法
        Class clz2 = str.getClass();
        // 獲得int類型Class實例
        Class clz3 = int.class;
        // 獲得Integer類型Class實例
        Class clz4 = Integer.class;
        System.out.println("clz2類名稱:" + clz2.getName());
        System.out.println("clz2是否爲接口:" + clz2.isInterface());
        System.out.println("clz2是否爲數組對象:" + clz2.isArray());
        System.out.println("clz2父類名稱:" + clz2.getSuperclass().getName());
        System.out.println("clz2是否爲基本類型:" + clz2.isPrimitive());
        System.out.println("clz3是否爲基本類型:" + clz3.isPrimitive());
        System.out.println("clz4是否爲基本類型:" + clz4.isPrimitive());
    }
}

運行結果如下:

clz2類名稱:java.lang.String
clz2是否爲接口:false
clz2是否爲數組對象:false
clz2父類名稱:java.lang.Object
clz2是否爲基本類型:false
clz3是否爲基本類型:true
clz4是否爲基本類型:false

注意上述代碼第 10 行和第 12 行的區別。int 是基本數據類型,所以輸出結果爲 true;Integer 是類,是引用數據類型,所以輸出結果爲 false。

12.3.2 java.lang.reflect 包

java.lang.reflect 包提供了反射中用到類,主要的類說明如下:

示例代碼如下:

public class ReflectionTest02 {
    public static void main(String[] args) {
        try {
            // 動態加載xx類的運行時對象
            Class c = Class.forName("java.lang.String");
            // 獲取成員方法集合
            Method[] methods = c.getDeclaredMethods();
            // 遍歷成員方法集合
            for (Method method : methods) {
                // 打印權限修飾符,如public、protected、private
                System.out.print(Modifier.toString(method.getModifiers()));
                // 打印返回值類型名稱
                System.out.print(" " + method.getReturnType().getName() + " ");
                // 打印方法名稱
                System.out.println(method.getName() + "();");
            }
        } catch (ClassNotFoundException e) {
            System.out.println("找不到指定類");
        }
    }
}

上述代碼第 5 行是通過 Class 的靜態方法forName(String)創建某個類的運行時對象,其中的參數是類全名字符串,如果在類路徑中找不到這個類則拋出 ClassNotFoundException 異常,見代碼第 17 行。

代碼第 7 行是通過 Class 的實例方法 getDeclaredMethods() 返回某個類的成員方法對象數組。代碼第 9 行是遍歷成員方法集合,其中的元素是 Method 類型。

代碼第 11 行的method.getModifiers()方法返回訪問權限修飾符常量代碼,是 int 類型,例如 1 代表 public,這些數字代表的含義可以通過Modifier.toString(int)方法轉換爲字符串。代碼第 13 行通過 Method 的 getReturnType() 方法獲得方法返回值類型,然後再調用 getName() 方法返回該類型的名稱。代碼第 15 行method.getName()返回方法名稱。

12.4 使用反射訪問類的成員

12.4.1 使用反射訪問構造方法

爲了能夠動態獲取對象構造方法的信息,首先需要通過下列方法之一創建一個 Constructor 類型的對象或者數組。

  • getConstructors()
  • getConstructor(Class<?>…parameterTypes)
  • getDeclaredConstructors()
  • getDeclaredConstructor(Class<?>…parameterTypes)

如果是訪問指定的構造方法,需要根據該構造方法的入口參數的類型來訪問。
例如,訪問一個入口參數類型依次爲 int 和 String 類型的構造方法,下面的兩種方式均可以實現。

objectClass.getDeclaredConstructor(int.class,String.class);
objectClass.getDeclaredConstructor(new Class[]{int.class,String.class});

常見API

Constructor 類的常用方法

方法名稱 說明
isVarArgs() 查看該構造方法是否允許帶可變數量的參數,如果允許,返回 true,否則返回false
getParameterTypes() 按照聲明順序以 Class 數組的形式獲取該構造方法各個參數的類型
getExceptionTypes() 以 Class 數組的形式獲取該構造方法可能拋出的異常類型
newInstance(Object … initargs) 通過該構造方法利用指定參數創建一個該類型的對象,如果未設置參數則表示採用默認無參的構造方法
setAccessiable(boolean flag) 如果該構造方法的權限爲 private,默認爲不允許通過反射利用 netlnstance()方法創建對象。如果先執行該方法,並將入口參數設置爲 true,則允許創建對象
getModifiers() 獲得可以解析出該構造方法所採用修飾符的整數

通過 java.lang.reflect.Modifier 類可以解析出 getMocMers() 方法的返回值所表示的修飾符信息。在該類中提供了一系列用來解析的靜態方法,既可以查看是否被指定的修飾符修飾,還可以字符串的形式獲得所有修飾符。

Modifier類的常用方法

靜態方法名稱 說明
isStatic(int mod) 如果使用 static 修飾符修飾則返回 true,否則返回 false
isPublic(int mod) 如果使用 public 修飾符修飾則返回 true,否則返回 false
isProtected(int mod) 如果使用 protected 修飾符修飾則返回 true,否則返回 false
isPrivate(int mod) 如果使用 private 修飾符修飾則返回 true,否則返回 false
isFinal(int mod) 如果使用 final 修飾符修飾則返回 true,否則返回 false
toString(int mod) 以字符串形式返回所有修飾符

Test

package Reflect;

import java.lang.reflect.Constructor;

/**
 * 通過反射獲取構造方法
 */
class Book{
    String name;
    int id,price;

    //構造方法,私有則我們無法調用
    private Book(){};
    //有參非私有
    protected Book(String name,int id){
        this.name = name;
        this.id = id;
    }
    //可變參數,並且聲明可能拋出的異常
    public Book(String... strings)throws NumberFormatException{
        if(0<=strings.length){
            id = Integer.valueOf(strings[0]);
        }
        else{
            price = Integer.valueOf(strings[1]);
        }

    }
    //輸出圖書的信息
    public void print(){
        System.out.println("name"+name+",id"+id+",price"+price);
    }
}

public class Practice3 {
    public static void main(String[]args){
        //獲取動態類Book,使用靜態量
        Class book = Book.class;
        //獲取構造方法,加s表示獲取所有的構造方法,不加則需要指明類型
        Constructor[] declareContructors = book.getDeclaredConstructors();

        //遍歷所有的構造
        for(int i=0;i<declareContructors.length;i++) {
            Constructor con = declareContructors[i];
            //判斷構造方法的參數是否可變
            System.out.println("查看是否允許帶可變數量的參數:" + con.isVarArgs());
            System.out.println("該構造方法的入口參數依次爲:");
            //獲取所有的參數類型
            Class[] parameterTypes = con.getParameterTypes();
            for (int j = 0; j < parameterTypes.length; j++) {
                System.out.println("" + parameterTypes[j]);
            }
            System.out.println("該構造方法可能拋出的異常類型爲:");
            Class[] exceptionTypes = con.getExceptionTypes();
            for (int j = 0; j < exceptionTypes.length; j++) {
                System.out.println(" " + parameterTypes[j]);
            }
            //創建一個未實例的Boo類的實例
            Book book1 = null;
            while (book1 == null) {
                try {//如果該成員的訪問權限爲private,則拋出異常
                    if (i == 1) {
                        //通過執行兩個帶參數的構造方法實例化book
                        book1 = (Book) con.newInstance("Jva教程", 10);
                    } else if (i == 2) {
                        book1 = (Book) con.newInstance();
                    } else{
                        Object[] parameters = new Object[]{new String[]{"100", "200"}};
                    book1 = (Book) con.newInstance(parameters);
                }
            }catch(Exception e){
                System.out.println("創建對象時拋出異常,下面執行setAccessible方法");
                con.setAccessible(true);//設置允許訪問私有成員
            }
        }
        book1.print();
        System.out.println("=============================\n");
        }
    }
}

在這裏插入圖片描述

12.4.2 通過反射機制訪問方法

要動態獲取一個對象方法的信息,首先需要通過下列方法之一創建一個 Method 類型的對象或者數組。

  • getMethods()
  • getMethods(String name,Class<?> …parameterTypes)
  • getDeclaredMethods()
  • getDeclaredMethods(String name,Class<?>…parameterTypes)

Method 類的常用方法

靜態方法名稱 說明
getName() 獲取該方法的名稱
getParameterType() 按照聲明順序以 Class 數組的形式返回該方法各個參數的類型
getReturnType() 以 Class 對象的形式獲得該方法的返回值類型
getExceptionTypes() 以 Class 數組的形式獲得該方法可能拋出的異常類型
invoke(Object obj,Object…args) 利用 args 參數執行指定對象 obj 中的該方法,返回值爲 Object 類型
isVarArgs() 查看該方法是否允許帶有可變數量的參數,如果允許返回 true,否則返回 false
getModifiers() 獲得可以解析出該方法所採用修飾符的整數

Demo_Test

package Reflect;

import java.lang.reflect.Method;

/**
 * Java通過反射執行方法(獲取方法)
 * 要動態獲取一個對象方法的信息,首先需要通過下列方法之一創建一個 Method 類型的對象或者數組。
 * getMethods()
 * getMethods(String name,Class<?> …parameterTypes)
 * getDeclaredMethods()
 * getDeclaredMethods(String name,Class<?>...parameterTypes)
 *
 * 如果是訪問指定的構造方法,需要根據該方法的入口參數的類型來訪問。
 */

class Book1 {
    // static 作用域方法
    static void staticMethod() {
        System.out.println("執行staticMethod()方法");
    }

    // public 作用域方法
    public int publicMethod(int i) {
        System.out.println("執行publicMethod()方法");
        return 100 + i;
    }

    // protected 作用域方法
    protected int protectedMethod(String s, int i) throws NumberFormatException {
        System.out.println("執行protectedMethod()方法");
        return Integer.valueOf(s) + i;
    }

    // private 作用域方法
    private String privateMethod(String... strings) {
        System.out.println("執行privateMethod()方法");

        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < sb.length(); i++) {
            sb.append(strings[i]);
        }
        return sb.toString();
    }
}

public class Practice4 {
    public static void main(String[] args) {
        // 獲取動態類Book1
        Book1 book = new Book1();
        Class class1 = book.getClass();
        // 獲取Book1類的所有方法
        Method[] declaredMethods = class1.getDeclaredMethods();
        for (int i = 0; i < declaredMethods.length; i++) {
            Method method = declaredMethods[i];//獲取方法
            System.out.println("方法名稱爲:" + method.getName());
            System.out.println("方法是否帶有可變數量的參數:" + method.isVarArgs());
            System.out.println("方法的參數類型依次爲:");
            // 獲取所有參數類型
            Class[] methodType = method.getParameterTypes();
            for (int j = 0; j < methodType.length; j++) {
                System.out.println(" " + methodType[j]);
            }
            // 獲取返回值類型
            System.out.println("方法的返回值類型爲:" + method.getReturnType());
            System.out.println("方法可能拋出的異常類型有:");
            // 獲取所有可能拋出的異常
            Class[] methodExceptions = method.getExceptionTypes();
            for (int j = 0; j < methodExceptions.length; j++) {
                System.out.println(" " + methodExceptions[j]);
            }
            boolean isTurn = true;
            while (isTurn) {
                try { // 如果該成員變量的訪問權限爲private,則拋出異常
                    isTurn = false;
                    if (method.getName().equals("staticMethod")) { // 調用沒有參數的方法
                        method.invoke(book);
                    } else if (method.getName().equals("publicMethod")) { // 調用一個參數的方法
                        System.out.println("publicMethod(10)的返回值爲:" + method.invoke(book, 10));
                    } else if (method.getName().equals("protectedMethod")) { // 調用兩個參數的方法
                        System.out.println("protectedMethod(\"10\",15)的返回值爲:" + method.invoke(book, "10", 15));
                    } else if (method.getName().equals("privateMethod")) { // 調用可變數量參數的方法
                        Object[] parameters = new Object[] { new String[] { "J", "A", "V", "A" } };
                        System.out.println("privateMethod()的返回值爲:" + method.invoke(book, parameters));
                    }
                } catch (Exception e) {
                    System.out.println("在設置成員變量值時拋出異常,下面執行setAccessible()方法");
                    method.setAccessible(true); // 設置爲允許訪問private方法
                    isTurn = true;

                }
            }
            System.out.println("=============================\n");
        }
    }
}

12.4.3 通過反射訪問成員變量

通過下列任意一個方法訪問成員變量時將返回 Field 類型的對象或數組。

  • getFields()
  • getField(String name)
  • getDeclaredFields()
  • getDeclaredField(String name)

上述方法返回的 Field 對象代表一個成員變量。例如,要訪問一個名稱爲 price 的成員變量,示例代碼如下:

object.getDeciaredField("price");

Field 類的常用方法

方法名稱 說明
getName() 獲得該成員變量的名稱
getType() 獲取表示該成員變量的 Class 對象
get(Object obj) 獲得指定對象 obj 中成員變量的值,返回值爲 Object 類型
set(Object obj, Object value) 將指定對象 obj 中成員變量的值設置爲 value
getlnt(0bject obj) 獲得指定對象 obj 中成員類型爲 int 的成員變量的值
setlnt(0bject obj, int i) 將指定對象 obj 中成員變量的值設置爲 i
setFloat(Object obj, float f) 將指定對象 obj 中成員變量的值設置爲 f
getBoolean(Object obj) 獲得指定對象 obj 中成員類型爲 boolean 的成員變量的值
setBoolean(Object obj, boolean b) 將指定對象 obj 中成員變量的值設置爲 b
getFloat(Object obj) 獲得指定對象 obj 中成員類型爲 float 的成員變量的值
setAccessible(boolean flag) 此方法可以設置是否忽略權限直接訪問 private 等私有權限的成員變量
getModifiers() 獲得可以解析出該方法所採用修飾符的整數

Test

package Reflect;

import java.lang.reflect.Field;

/**
 * 通過反射訪問成員變量
 *
 * 通過下列任意一個方法訪問成員變量時將返回 Field 類型的對象或數組。
 * getFields()
 * getField(String name)
 * getDeclaredFields()
 * getDeclaredField(String name)
 *
 * 上述方法返回的 Field 對象代表一個成員變量。
 */

class Book2 {
    String name;
    public int id;
    private float price;
    protected boolean isLoan;
}

public class Practice5 {
    public static void main(String[] args) {
        Book2 book = new Book2();
        // 獲取動態類Book2
        Class class1 = book.getClass();
        // 獲取Book2類的所有成員
        Field[] declaredFields = class1.getDeclaredFields();
        // 遍歷所有的成員
        for(int i = 0;i < declaredFields.length;i++) {
            // 獲取類中的成員變量
            Field field = declaredFields[i];
            System.out.println("成員名稱爲:" + field.getName());
            Class fieldType = field.getType();
            System.out.println("成員類型爲:" + fieldType);
            boolean isTurn = true;
            while(isTurn) {
                try {
                    // 如果該成員變量的訪問權限爲private,則拋出異常
                    isTurn = false;
                    System.out.println("修改前成員的值爲:" + field.get(book));
                    // 判斷成員類型是否爲int
                    if(fieldType.equals(int.class)) {
                        System.out.println("利用setInt()方法修改成員的值");
                        field.setInt(book, 100);
                    } else if(fieldType.equals(float.class)) {
                        // 判斷成員變量類型是否爲float
                        System.out.println("利用setFloat()方法修改成員的值");
                        field.setFloat(book, 29.815f);
                    } else if(fieldType.equals(boolean.class)) {
                        // 判斷成員變量是否爲boolean
                        System.out.println("利用setBoolean()方法修改成員的值");
                        field.setBoolean(book, true);
                    } else {
                        System.out.println("利用set()方法修改成員的值");
                        field.set(book, "Java編程");
                    }
                    System.out.println("修改後成員的值爲:" + field.get(book));
                } catch (Exception e) {
                    System.out.println("在設置成員變量值時拋出異常,下面執行setAccessible()方法");
                    field.setAccessible(true);
                    isTurn = true;
                }
            }
            System.out.println("=============================\n");
        }
    }
}

運行結果:
在這裏插入圖片描述

12.5 通過反射操作泛型

12.5.1 泛型和 Class 類

使用 Class 泛型可以避免強制類型轉換。例如,下面提供一個簡單的對象工廠,該對象工廠可以根據指定類來提供該類的實例。

public class ObjectFactory {
    public static Object getInstance(String clsName) {//參數爲類的名
        try {
            // 創建指定類對應的Class對象
            Class cls = Class.forName(clsName);//動態創建對象
            // 返回使用該Class對象創建的實例
            return cls.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

上面程序中第 5 、7 行代碼根據指定的字符串類型創建了一個新對象,但這個對象的類型是 Object,因此當需要使用 ObjectFactory 的 getInstance() 方法來創建對象時,代碼如下:

// 獲取實例後需要強制類型轉換
Date d = (Date)ObjectFactory.getInstance("java.util.Date");
JFrame f = (JFrame)ObjectFactory .getInstance("java.util.Date");

上面代碼在編譯時不會有任何問題,但運行時將拋出 ClassCastException(強制類型轉換異常),因爲程序試圖將一個 Date 對象轉換成 JFrame 對象。

如果將上面的 ObjectFactory 工廠類改寫成使用泛型後的 Class,就可以避免這種情況。

package Reflect;

import javax.swing.*;
import java.util.Date;

public class ObjectFactory {

        public static <T> T getInstance(Class<T> cls) {//使用靜態量 xxx.class
            try {
                return cls.newInstance();
                //注意Class.newInstance在 java9分離了
      //此方法傳播由nullary構造函數拋出的任何異常,包括已檢查的異常。使用此方法可以有效地繞過編譯器執行的編譯時異常檢查。
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        public static void main(String[] args) {
            // 獲取實例後無須類型轉換
            Date d = ObjectFactory.getInstance(Date.class);
            JFrame f = ObjectFactory.getInstance(JFrame.class);
        }
}

在上面程序的 getInstance() 方法中傳入一個 Class<T> 參數,這是一個泛型化的 Class 對象,調用該 Class 對象的 newInstance() 方法將返回一個 T 對象,如程序中第 4 行代碼所示。接下來當使用 ObjectFactory2 工廠類的 getInstance() 方法來產生對象時,無須使用強制類型轉換,系統會執行更嚴格的檢查,不會出現 ClassCastException 運行時異常。

使用 Array 類來創建數組時:

// 使用Array的newInstance方法來創建一個數組
Object arr = Array.newInstance(String.class, 10);

對於上面的代碼其實使用並不是非常方便,因爲 newInstance() 方法返回的確實是一個 String[] 數組,而不是簡單的 Object 對象。如果需要將 arr 對象當成 String[] 數組使用,則必須使用強制類型轉換,但是這是不安全的操作。

奇怪的是,Array 的 newInstance() 方法簽名(方法簽名由方法名稱和參數列表組成)爲如下形式:

public static Object newInstance(Class<?> componentType, int... dimensions)

在這個方法簽名中使用了 Class<?> 泛型,但並沒有真正利用這個泛型。如果將該方法簽名改爲如下形式:

public static <T> T[] newInstance(Class<T> componentType, int length)

這樣就可以在調用該方法後無需強制類型轉換了。不過,這個方法暫時只能創建一維數組,也就是不能利用可變個數的參數優勢了。

爲了示範泛型的優勢,可以對 Array 的 newInstance() 方法進行包裝。

public class CrazyitArray {
    // 對Array的newInstance方法進行包裝
    @SuppressWarnings("unchecked")
    public static <T> T[] newInstance(Class<T> componentType, int length) {
        return (T[]) Array.newInstance(componentType, length); 
    }
    public static void main(String[] args) {
        // 使用 CrazyitArray 的 newInstance()創建一維數組
        String[] arr = CrazyitArray.newInstance(String.class, 10);
        // 使用 CrazyitArray 的 newInstance()創建二維數組
        // 在這種情況下,只要設置數組元素的類型是int[]即可
        int[][] intArr = CrazyitArray.newInstance(int[].class, 5);
        arr[5] = "Java教程";
        // intArr是二維數組,初始化該數組的第二個數組元素
        // 二維數組的元素必須是一維數組
        intArr[1] = new int[]{ 23, 12 };
        System.out.println(arr[5]);
        System.out.println(intArr[1][1]);
    }
}

上面程序中第 4、5、6、10 和 13 定義的 newInstance() 方法對 Array 類提供的 newInstance() 方法進行了包裝,將方法簽名改成了 public static <T> T[] newInstance(Class<T> componentType, int length),這就保證程序通過該 newInstance() 方法創建數組時的返回值就是數組對象,而不是 Object 對象,從而避免了強制類型轉換。

@SuppressWarnings(“unchecked”) 告訴編譯器忽略 unchecked 警告信息,如使用 List,ArrayList 等未進行參數化產生的警告信息。程序在第 5 行代碼處將會有一個 unchecked 編譯警告,所以程序使用了 @SuppressWarnings 來抑制這個警告信息。

12.5.2 使用反射來獲取泛型信息

通過指定類對應的 Class 對象,可以獲得該類裏包含的所有成員變量,不管該成員變量是使用 private 修飾,還是使用 public 修飾。獲得了成員變量對應的 Field 對象後,就可以很容易地獲得該成員變量的數據類型,即使用如下代碼即可獲得指定成員變量的類型。

// 獲取成員變量 f 的類型
Class<?> a = f.getType();

但這種方式只對普通類型的成員變量有效。如果該成員變量的類型是有泛型類型的類型,如 Map<String, Integer>類型,則不能準確地得到該成員變量的泛型參數。

爲了獲得指定成員變量的泛型類型,應先使用如下方法來獲取該成員變量的泛型類型。

// 獲得成員變量f的泛型類型
Type gType = f.getGenericType();

然後將 Type 對象強制類型轉換爲 ParameterizedType 對象,ParameterizedType 代表被參數化的類型,也就是增加了泛型限制的類型。ParameterizedType 類提供瞭如下兩個方法。

getRawType():返回沒有泛型信息的原始類型。
getActualTypeArguments():返回泛型參數的類型。

下面是一個獲取泛型類型的完整程序。

public class GenericTest {
    private Map<String, Integer> score;
    public static void main(String[] args) throws Exception {
        Class<GenericTest> clazz = GenericTest.class;
        Field f = clazz.getDeclaredField("score");
        // 直接使用getType()取出類型只對普通類型的成員變量有效
        Class<?> a = f.getType();
        // 下面將看到僅輸出java.util.Map
        System.out.println("score 的類型是:" + a);
        // 獲得成員變量f的泛型類型
        Type gType = f.getGenericType();
        // 如果 gType 類型是 ParameterizedType對象
        if (gType instanceof ParameterizedType) {
            // 強制類型轉換
            ParameterizedType pType = (ParameterizedType) gType;
            // 獲取原始類型
            Type rType = pType.getRawType();
            System.out.println("原始類型是:" + rType);
            // 取得泛型類型的泛型參數
            Type[] tArgs = pType.getActualTypeArguments();
            System.out.println("泛型信息是:");
            for (int i = 0; i < tArgs.length; i++) {
                System.out.println("第" + i + "個泛型類型是:" + tArgs[i]);
            }
        } else {
            System.out.println("獲取泛型類型出錯!");
        }
    }
}

上面程序中的第 12、16、18 和 21 行代碼就是取得泛型類型的關鍵代碼。運行上面程序,將看到如下運行結果:

score 的類型是:interface java.util.Map
原始類型是:interface java.util.Map
泛型信息是:0個泛型類型是:class java.lang.String
第1個泛型類型是:class java.lang.Integer

從上面的運行結果可以看出,使用 getType() 方法只能獲取普通類型的成員變量的數據類型。對於增加了泛型的成員變量,應該使用 getGenericType() 方法來取得其類型。

Type 也是 java.lang.reflect 包下的一個接口,該接口代表所有類型的公共高級接口,Class 是 Type 接口的實現類。Type 包括原始類型、參數化類型、數組類型、類型變量和基本類型等。

12.5.3 反射操作泛型接口

通過反射獲取到抽象類或者接口中泛型信息的操作也是很常見的。實際上開發中,解析後臺數據的Json數據,生成對應的泛型實體類,會用到反射獲取泛型信息的操作。

相應的API:

  • getGenericInterfaces()獲取到泛型接口的Type數組。
  • getActualTypeArguments()獲取到泛型接口的實際類型。

12.6 通過反射創建數組(代碼展示)

1、什麼是ArrayList ?ArrayList就是傳說中的動態數組

2、Array如果不是反射裏邊的Array,是不能動態分配空間的。
3、實例代碼演示創建多維數組

package Reflect;


import java.lang.reflect.Array;
import static java.lang.reflect.Array.newInstance;//靜態導入

/**
 * 使用反射機制動態創建數組並訪問數組
 * java.lang.reflect包提供了一個Array類,使用Array類可以動態地創建和訪問數組。
 * static newInstance(Class<?> componentType, int length)方法可創建數組。
 *  componentType用以指定數組的元素類型,length用以指定數組長度。該方法返回一個Object,可考慮使用強制轉換。
 *
 * static native xxx getXxx(Object array, int index)方法可獲取數組某一元素。
 *  array用以指定數組,index用以指定元素下標。xxx表示int和short等基本類型,若是引用類型的數組,則應去掉xxx。
 *
 * static native void setXxx(Object array, int index, xxx val)方法可爲數組某一元素賦值。
 *  array用以指定數組,index用以指定要賦值的元素的下標,val爲要賦的值。該方法同樣返回一個Object,可考慮使用強制轉換。
 *
 *
 *  reflect.Array類支持多維數組
 *
 * static Object newInstance(Class<?> componentType, int... dimensions)
 *
 * 備註:使用反射創建對象,效率較低
 *
 */

public class Array_ByReflect {
    public static void main(String[]args){
        String []array = (String[])newInstance(String.class,2);

        //set數組元素
        Array.set(array,0,"ABC");
        Array.set(array,1,"CBA");

        //get數組元素
        for(int i=0;i<array.length;i++){
            String str = (String)Array.get(array,i);//返回的是Object對象
            System.out.println(str);
        }

        //演示多維數組,數組的shape(4,5,4)三維
        Object thirdArr = Array.newInstance(int.class, 4, 5, 4);
        Object firstArr = Array.get(thirdArr, 3);//第一維度
        Array.set(firstArr, 2, new int[] {5, 6});//可以一次設置元素的值

        Object secArr = Array.get(firstArr, 4);//第二維度
        Array.set(secArr, 1, 2);
        Array.set(secArr, 3, 9);
        int[][][] strArr = (int[][][]) thirdArr;
        System.out.println(strArr[3][2][0]);
        System.out.println(strArr[3][2][1]);
        System.out.println(strArr[3][4][1]);
        System.out.println(strArr[3][4][3]);




    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章