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ì)通過(guò)加載番甩,連接侵贵,初始化三步來(lái)實(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í)例蕾域。
  • 訪問(wèn)類的靜態(tài)變量,或者為靜態(tài)變量賦值到旦。
  • 調(diào)用類的靜態(tài)方法旨巷。
  • 使用反射方式來(lái)強(qiáng)制創(chuàng)建某個(gè)類或接口對(duì)應(yīng)的 java.lang.Class 對(duì)象。
  • 初始化某個(gè)類的子類添忘。
  • 直接使用 java.exe 命令來(lái)運(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í)加載來(lái)自 java 命令的 class 文件众辨,以及 classpath 環(huán)境變量所指定的 jar 包和類路徑端三。也就是說(shuō),平時(shí)我們寫的 java 文件鹃彻,編譯后生成的 class 文件郊闯,都是通過(guò)該加載器進(jìn)行加載的。

通過(guò)這些描述我們就可以知道我們常用的東西的加載都是由誰(shuí)來(lái)完成的蛛株。

那么团赁,我們?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 語(yǔ)言的反射機(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. 通過(guò)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()

    • 無(wú)參構(gòu)造
    try {
        Constructor constructor = clazz.getConstructor();
        Object object = constructor.newInstance();// 無(wú)參構(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)造是類似的哗戈,只不過(guò)將 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 非法訪問(wèn)異常暇仲,需要使用暴力訪問(wèn),即設(shè)置 setAccessible(true)副渴。

    try {
        Constructor constructor = clazz.getDeclaredConstructor(int.class);
        constructor.setAccessible(true);// 暴力訪問(wèn)
        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 非法訪問(wèn)異常斥滤,需要使用暴力訪問(wèn)将鸵,即設(shè)置 setAccessible(true)

    try {
        Constructor constructor = clazz.getConstructor();
        Object object = constructor.newInstance();
        Field name = clazz.getDeclaredField("name");// 獲取 name 成員變量
        name.setAccessible(true);// 暴力訪問(wèn)佑颇,可訪問(wèn)私有成員變量
        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()

    • 無(wú)參數(shù)痒筒、無(wú)返回值
    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ù)、無(wú)返回值
    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 非法訪問(wèn)異常螟左,需要使用暴力訪問(wèn)啡浊,即設(shè)置 setAccessible(true)
    try {
        Constructor constructor = clazz.getConstructor();
        Object object = constructor.newInstance();
        Method hello = clazz.getDeclaredMethod("hello");// 私有成員方法
        hello.setAccessible(true);// 暴力訪問(wèn)
        hello.invoke(object);// 調(diào)用 hello 方法
    } catch (NoSuchMethodException | InstantiationException
            | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
    

反射的應(yīng)用

一胶背、跳過(guò)泛型檢查

向 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)系作者
  • 序言:七十年代末奔誓,一起剝皮案震驚了整個(gè)濱河市斤吐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌厨喂,老刑警劉巖和措,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蜕煌,居然都是意外死亡派阱,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門斜纪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)贫母,“玉大人文兑,你說(shuō)我怎么就攤上這事∠倭樱” “怎么了绿贞?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)橘原。 經(jīng)常有香客問(wèn)我籍铁,道長(zhǎng),這世上最難降的妖魔是什么趾断? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任寨辩,我火速辦了婚禮,結(jié)果婚禮上歼冰,老公的妹妹穿的比我還像新娘。我一直安慰自己耻警,他們只是感情好隔嫡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著甘穿,像睡著了一般腮恩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上温兼,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天秸滴,我揣著相機(jī)與錄音,去河邊找鬼募判。 笑死荡含,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的届垫。 我是一名探鬼主播释液,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼装处!你這毒婦竟也來(lái)了误债?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤妄迁,失蹤者是張志新(化名)和其女友劉穎寝蹈,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體登淘,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡箫老,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了黔州。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片槽惫。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡周叮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出界斜,到底是詐尸還是另有隱情仿耽,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布各薇,位于F島的核電站项贺,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏峭判。R本人自食惡果不足惜开缎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望林螃。 院中可真熱鬧奕删,春花似錦、人聲如沸疗认。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)横漏。三九已至谨设,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間缎浇,已是汗流浹背扎拣。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留素跺,地道東北人二蓝。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像指厌,于是被迫代替她去往敵國(guó)和親侣夷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容