Java 反射機(jī)制

不論是 Java 開發(fā) 還是 Android 開發(fā),反射、泛型、注解 都是架構(gòu)設(shè)計(jì)中很重要的一個(gè)知識(shí)點(diǎn)。

為了更好的理解反射,需要先簡(jiǎn)單了解一些類加載器相關(guān)的知識(shí)。

類加載器

一、類的初始化

當(dāng)程序要使用某個(gè)類時(shí),如果該類還未被加載到內(nèi)存中,則系統(tǒng)會(huì)通過加載,連接,初始化三步來實(shí)現(xiàn)對(duì)這個(gè)類進(jìn)行初始化。

  1. 加載
    就是指將 class 文件讀入內(nèi)存,并為之創(chuàng)建一個(gè) Class 對(duì)象,任何類被使用時(shí)系統(tǒng)都會(huì)建立一個(gè) Class 對(duì)象。
  2. 連接
  • 驗(yàn)證:是否有正確的內(nèi)部結(jié)構(gòu),并和其他類協(xié)調(diào)一致。
  • 準(zhǔn)備:負(fù)責(zé)為類的靜態(tài)成員分配內(nèi)存,并設(shè)置默認(rèn)初始化值,靜態(tài)隨著類的加載而加載。
  • 解析:將類的二進(jìn)制數(shù)據(jù)中的符號(hào)引用替換為直接引用。
  1. 初始化
    為堆棧開辟內(nèi)存,默認(rèn)初始化,構(gòu)造初始化,等等。

二、類初始化時(shí)機(jī)

  • 創(chuàng)建類的實(shí)例。
  • 訪問類的靜態(tài)變量,或者為靜態(tài)變量賦值。
  • 調(diào)用類的靜態(tài)方法。
  • 使用反射方式來強(qiáng)制創(chuàng)建某個(gè)類或接口對(duì)應(yīng)的 java.lang.Class 對(duì)象。
  • 初始化某個(gè)類的子類。
  • 直接使用 java.exe 命令來運(yùn)行某個(gè)主類。

三、類加載器

負(fù)責(zé)將 .class 文件加載到內(nèi)在中,并為之生成對(duì)應(yīng)的 Class 對(duì)象。

  • Bootstrap ClassLoader
    根類加載器,也被稱為引導(dǎo)類加載器,負(fù)責(zé) Java 核心類的加載,比如 System、String 等。在 JDK 中 JRE 的 lib 目錄下 rt.jar 文件中。

  • Extension ClassLoader
    擴(kuò)展類加載器,負(fù)責(zé) JRE 的擴(kuò)展目錄中 jar 包的加載,在 JDK 中 JRE 的 lib 目錄下 ext 目錄。

  • System ClassLoader
    系統(tǒng)類加載器,負(fù)責(zé)在 JVM 啟動(dòng)時(shí)加載來自 java 命令的 class 文件,以及 classpath 環(huán)境變量所指定的 jar 包和類路徑。也就是說,平時(shí)我們寫的 java 文件,編譯后生成的 class 文件,都是通過該加載器進(jìn)行加載的。

通過這些描述我們就可以知道我們常用的東西的加載都是由誰來完成的。

那么,我們?nèi)绾问褂眠@些class文件中的內(nèi)容呢?這就是反射要研究的內(nèi)容。

反射

Java 反射機(jī)制是在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法和屬性;這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能成為 Java 語言的反射機(jī)制。

一、獲取 Class 類對(duì)象

要想解剖一個(gè)類,必須先要獲取到該類的字節(jié)碼文件對(duì)象。而解剖使用的就是 Class 類中的方法。所以先要獲取到每一個(gè)字節(jié)碼文件對(duì)應(yīng)的 Class 類型的對(duì)象。

有三種方式獲取,下面用這個(gè) Book.java 舉例:

public class Book {

    private String name;
    public int price;

    public Book() {
    }

    Book(String name) {
        this.name = name;
    }

    public Book(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public void show() {
        System.out.println("show");
    }

    public void function(String s) {
        System.out.println("function: " + s);
    }

    public String returnValue(String name, int price) {
        return name + " - " + price;
    }

    private void hello() {
        System.out.println("hello");
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}
  1. Object 類的 getClass() 方法
    在可以獲取到該實(shí)例對(duì)象的情況下,采用該方法。

    // 方式一
    Book book = new Book();
    Class c1 = book.getClass();
    
  2. 數(shù)據(jù)類型的靜態(tài) class 屬性
    在可以導(dǎo)入該類的情況下,采用該方法。

    // 方式二
    Class c2 = Book.class;
    
  3. 通過Class類的靜態(tài)方法 forName(String className)
    在得知完整類名的情況下,采用該方法。

    // 方式三
    try {
        Class c3 = Class.forName("com.ff.reflect.Book");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    

開發(fā)中經(jīng)常會(huì)使用方式三,首先,方式三可以結(jié)合配置文件使用,從配置文件中獲取完整類名;其次,很多情況下不能獲取實(shí)例對(duì)象和導(dǎo)入類。

二、獲取構(gòu)造方法

前提條件就是先要獲取到 Class 文件對(duì)象

Class clazz = null;
try {
    clazz = Class.forName("com.ff.reflect.Book");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
if (clazz == null) {
    return;
}
獲取全部構(gòu)造方法
  1. 獲取所有公共構(gòu)造方法 getConstructors()
    可以獲取到 public 修飾的構(gòu)造方法。

    Constructor[] constructors = clazz.getConstructors();
    for (Constructor constructor : constructors) {
        System.out.println(constructor);
    }
    

    打印結(jié)果:

    public com.ff.reflect.Book(java.lang.String,int)
    public com.ff.reflect.Book()
    
  2. 獲取所有構(gòu)造方法 getDeclaredConstructors()
    可以獲取到全部構(gòu)造方法,包括 public、protected、private 以及默認(rèn)修飾的構(gòu)造方法。

    Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
    for (Constructor declaredConstructor : declaredConstructors) {
        System.out.println(declaredConstructor);
    }
    

    打印結(jié)果:

    private com.ff.reflect.Book(int)
    com.ff.reflect.Book(java.lang.String)
    public com.ff.reflect.Book(java.lang.String,int)
    public com.ff.reflect.Book()
    
獲取單個(gè)構(gòu)造方法

開發(fā)中我們一般需要使用一種構(gòu)造方法,所以下面方式更為常用。

  1. 獲取單個(gè)公共構(gòu)造方法 getConstructor()

    • 無參構(gòu)造
    try {
        Constructor constructor = clazz.getConstructor();
        Object object = constructor.newInstance();// 無參構(gòu)造
        System.out.println(object);
    } catch (NoSuchMethodException | IllegalAccessException
            | InstantiationException | InvocationTargetException e) {
        e.printStackTrace();
    }
    
    • 帶參構(gòu)造
    try {
        Constructor constructor = clazz.getConstructor(String.class, int.class);
        Object object = constructor.newInstance("Java", 18);// 兩個(gè)參數(shù)的構(gòu)造
        System.out.println(object);
    } catch (NoSuchMethodException | InstantiationException
            | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
    
  2. 獲取單個(gè)非公共構(gòu)造方法 getDeclaredConstructor()
    和上面獲取公共構(gòu)造是類似的,只不過將 getConstructor() 替換為 getDeclaredConstructor() 就可以獲取非 public 的構(gòu)造方法了。

    try {
        Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class);
        Object object = declaredConstructor.newInstance("Java");
        System.out.println(object);
    } catch (NoSuchMethodException | InstantiationException
            | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
    

    需要注意的是,如果反射得到的是私有構(gòu)造方法,那么直接調(diào)用會(huì)報(bào): IllegalAccessException 非法訪問異常,需要使用暴力訪問,即設(shè)置 setAccessible(true)。

    try {
        Constructor constructor = clazz.getDeclaredConstructor(int.class);
        constructor.setAccessible(true);// 暴力訪問
        Object object = constructor.newInstance(18);// 私有構(gòu)造
        System.out.println(object);
    } catch (NoSuchMethodException | InstantiationException
            | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
    

三、獲取成員變量

與上面獲取構(gòu)造方法大同小異

獲取全部成員變量
  1. 獲取所有公共成員變量 getFields()
    Field[] fields = clazz.getFields();
    for (Field field : fields) {
        System.out.println(field);
    }
    
  2. 獲取所有成員變量 getDeclaredFields()
    Field[] declaredFields = clazz.getDeclaredFields();
    for (Field declaredField : declaredFields) {
        System.out.println(declaredField);
    }
    
獲取單個(gè)成員變量
  1. 獲取單個(gè)公共成員變量 getField()

    try {
        Constructor constructor = clazz.getConstructor();
        Object object = constructor.newInstance();
        Field price = clazz.getField("price");// 獲取 price 成員變量
        price.set(object, 18);// 修改成員變量的值
        System.out.println(object);
    } catch (NoSuchFieldException | NoSuchMethodException | InstantiationException
            | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
    
  2. 獲取單個(gè)非公共成員變量 getDeclaredField()

    try {
        Constructor constructor = clazz.getConstructor();
        Object object = constructor.newInstance();
        Field name = clazz.getDeclaredField("name");// 獲取 name 成員變量
        name.set(object, "Java");// 修改成員變量的值
        System.out.println(object);
    } catch (NoSuchFieldException | NoSuchMethodException | InstantiationException
            | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
    

    需要注意的是,如果反射得到的是私有成員變量,那么直接調(diào)用會(huì)報(bào): IllegalAccessException 非法訪問異常,需要使用暴力訪問,即設(shè)置 setAccessible(true)。

    try {
        Constructor constructor = clazz.getConstructor();
        Object object = constructor.newInstance();
        Field name = clazz.getDeclaredField("name");// 獲取 name 成員變量
        name.setAccessible(true);// 暴力訪問,可訪問私有成員變量
        name.set(object, "Java");// 修改成員變量的值
        System.out.println(object);
    } catch (NoSuchFieldException | NoSuchMethodException | InstantiationException
            | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
    

四、獲取成員方法

獲取全部成員方法
  1. 獲取所有公共成員方法,包括父類 getMethods()

    Method[] methods = clazz.getMethods();
    for (Method method : methods) {
        System.out.println(method);
    }
    
  2. 獲取所有成員方法,不包含父類 getDeclaredMethods()

    Method[] declaredMethods = clazz.getDeclaredMethods();
    for (Method declaredMethod : declaredMethods) {
        System.out.println(declaredMethod);
    }
    
獲取單個(gè)成員方法
  1. 獲取單個(gè)公共成員方法 getMethod()

    • 無參數(shù)、無返回值
    try {
        Constructor constructor = clazz.getConstructor();
        Object object = constructor.newInstance();
        Method show = clazz.getMethod("show");// show 方法
        show.invoke(object);// 調(diào)用 show 方法
    } catch (NoSuchMethodException | InstantiationException
            | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
    
    • 帶參數(shù)、無返回值
    try {
        Constructor constructor = clazz.getConstructor();
        Object object = constructor.newInstance();
        Method function = clazz.getMethod("function", String.class);// function 方法
        function.invoke(object, "hello");// 調(diào)用 function 方法,傳參 hello
    } catch (NoSuchMethodException | InstantiationException
            | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
    
  • 帶多個(gè)參數(shù),有返回值
    try {
        Constructor constructor = clazz.getConstructor();
        Object object = constructor.newInstance();
        Method returnValue = clazz.getMethod("returnValue", String.class, int.class);// returnValue 方法
        Object string = returnValue.invoke(object, "Java", 18);// 調(diào)用 returnValue 方法,傳參,得到方法返回值
        System.out.println(string);// 打印 returnValue 方法返回值
    } catch (NoSuchMethodException | InstantiationException
            | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
    
  1. 獲取單個(gè)非公共成員方法 getDeclaredMethod()
    需要注意的是,如果反射得到的是私有成員方法,那么直接調(diào)用會(huì)報(bào): IllegalAccessException 非法訪問異常,需要使用暴力訪問,即設(shè)置 setAccessible(true)。
    try {
        Constructor constructor = clazz.getConstructor();
        Object object = constructor.newInstance();
        Method hello = clazz.getDeclaredMethod("hello");// 私有成員方法
        hello.setAccessible(true);// 暴力訪問
        hello.invoke(object);// 調(diào)用 hello 方法
    } catch (NoSuchMethodException | InstantiationException
            | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
    

反射的應(yīng)用

一、跳過泛型檢查

向 ArrayList<Integer> 中添加字符串?dāng)?shù)據(jù)。
由于泛型只在編譯期間生效,而反射是在運(yùn)行期間調(diào)用,所以可以利用這兩點(diǎn)進(jìn)行實(shí)現(xiàn):

/**
 * 向 ArrayList<Integer> 中添加字符串?dāng)?shù)據(jù)
 */
private static void test() {
    ArrayList<Integer> array = new ArrayList<>();

    Class<? extends ArrayList> aClass = array.getClass();
    try {
        Method add = aClass.getMethod("add", Object.class);
        add.invoke(array, "hello");
        add.invoke(array, "world");
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
    System.out.println(array);
}

二、通用工具類

設(shè)置某個(gè)對(duì)象的某個(gè)屬性為指定值:
public void setProperty(Object obj, String propertyName, Object value){},
此方法可將obj對(duì)象中名為propertyName的屬性的值設(shè)置為value。

public class Utils {

    /**
     * 設(shè)置某個(gè)對(duì)象的某個(gè)屬性為指定值
     *
     * @param obj          對(duì)象
     * @param propertyName 屬性
     * @param value        值
     */
    public static void setProperty(Object obj, String propertyName, Object value) {
        Class<?> aClass = obj.getClass();
        try {
            Field declaredField = aClass.getDeclaredField(propertyName);
            declaredField.setAccessible(true);
            declaredField.set(obj, value);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

使用工具類:

private static void test() {
    Book book = new Book();
    Utils.setProperty(book, "name", "Java");
    Utils.setProperty(book, "price", 18);
    System.out.println(book);
}

在架構(gòu)設(shè)計(jì)中的應(yīng)用也很常見,比如動(dòng)態(tài)代理等等,就不在這里展開了。

至此,基本的 Java 反射機(jī)制都已經(jīng)介紹完了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容