Java反射機(jī)制

導(dǎo)語(yǔ)

反射機(jī)制是Java程序開發(fā)語(yǔ)言的重要特征之一。

主要內(nèi)容

  • 認(rèn)識(shí)反射
  • 理解反射作用
  • 利用反射來(lái)調(diào)用類的結(jié)構(gòu)

具體內(nèi)容

認(rèn)識(shí)反射

反射的話先通過“反”來(lái)理解窥妇,既然有“反”就有“正”舷胜,在正常情況下,一定是先有類而后再產(chǎn)生對(duì)象
所謂的“反”就是指可以利用對(duì)象找到對(duì)象的出處活翩,在Object類里面提供有一個(gè)方法:

取得Class對(duì)象:public final Class<?> getClass()烹骨。

范例:觀察反

import java.util.Date;  // 先有類
public class TestDemo {
    public static void main(String args[]) {
        Date date = new Date(); // 產(chǎn)生對(duì)象
        System.out.println(date.getClass());
    }
}

輸出結(jié)果

class java.util.Date

發(fā)現(xiàn)調(diào)用了getClass()方法后的輸出就輸出了類的完整名稱,等于是找到了對(duì)象的出處材泄。

Class類對(duì)象實(shí)例化

java.lang.Class是一個(gè)類沮焕,這個(gè)類是反射操作的源頭,即:所有的反射都要從此類開始進(jìn)行脸爱,而最關(guān)鍵的是這個(gè)類有三種實(shí)例化方式:

  • 第一種:調(diào)用Object類中的getClass()方法遇汞。
import java.util.Date;
public class TestDemo {
    public static void main(String args[]) {
        Date date = new Date();
        Class<?> cls = date.getClass();
        System.out.println(cls);
    }
}
  • 第二種:使用“類.class”取得。
import java.util.Date;
public class TestDemo {
    public static void main(String args[]) {
        Class<?> cls = Date.class;
        System.out.println(cls);
    }
}

之前是在產(chǎn)生了類的實(shí)例化對(duì)象之后取得的Class對(duì)象簿废,但是此時(shí)并沒有實(shí)例化對(duì)象的產(chǎn)生空入。

  • 第三種:調(diào)用Class類提供的一個(gè)方法。實(shí)例化Class對(duì)象:public static Class<?> forName(String className) throws ClassNotFoundException族檬。
public class TestDemo {
    public static void main(String args[]) {
        Class<?> cls = Class.forName("java.util.Date);
        System.out.println(cls);
    }
}

此時(shí)可以不使用import語(yǔ)句導(dǎo)入一個(gè)明確的類歪赢,而類名稱是采用字符串的形式進(jìn)行描述的。

反射實(shí)例化對(duì)象

當(dāng)拿到一個(gè)類的時(shí)候单料,肯定要直接使用關(guān)鍵字new進(jìn)行對(duì)象的實(shí)例化操作埋凯,這屬于習(xí)慣 性的做法,但是如果有了Class類對(duì)象扫尖,那么就可以做到白对,利用反射來(lái)實(shí)現(xiàn)對(duì)象實(shí)例化操作:

  • 實(shí)例化對(duì)象方法:public T newInstance() throws throws InstantiationException, IllegalAccessException。

范例:利用反射實(shí)例化對(duì)象

package cn.cc.demo;
class Book {
    public Book() {
        System.out.println("Book類的無(wú)參構(gòu)造方法");
    }
    @Override
    public String toString() {
        return "這是一本書换怖!";
    }
}
public class TestDemo {
    public static void main(String args[]) {
        Class<?> cls = Class.forName("cn.cc.demo.Book");
        Object obj = cls.newInstance();  // 相當(dāng)于使用new調(diào)用無(wú)參構(gòu)造實(shí)例化
        Book book = (Book) obj;
        System.out.println(book);
    }
}

輸出結(jié)果

Book類的無(wú)參構(gòu)造方法
這是一本書甩恼!

有了反射之后,以后進(jìn)行對(duì)象實(shí)例化的操作不再只是單獨(dú)的依靠關(guān)鍵字new完成了沉颂,反射也同樣可以完成条摸,但是這并不表示new就被完全取代了。

在任何的開發(fā)之中铸屉,new是造成耦合的最大元兇钉蒲,一切的耦合都起源于new。

范例:觀察工廠設(shè)計(jì)模式

package cn.cc.test;

interface Fruit {
    public void eat();
}

public class Apple implements Fruit {
    @Override
    public void eat() {
        System.out.println("吃蘋果彻坛!");
    }
}

public class Factory {
    public static Fruit getInstance(String className) {
        if("apple".equals(className)) {
            return new Apple();
        }
        return null;
    }
}

public class TestFactory {
    public static void main(String args[]) {
        Fruit f = Factory.getInstance("apple");
        f.eat();
    }
}

如果此時(shí)增加了Fruit接口子類顷啼,那么就表示程序要修改工廠類。

package cn.cc.test;

interface Fruit {
    public void eat();
}

public class Apple implements Fruit {
    @Override
    public void eat() {
        System.out.println("吃蘋果昌屉!");
    }
}

public class Orange implements Fruit {
    @Override
    public void eat() {
        System.out.println("吃橘子钙蒙!");
    }
}

public class Factory {
    public static Fruit getInstance(String className) {
        if("apple".equals(className)) {
            return new Apple();
        } else if() {
            return new Orange();
        } else {
            return null;
        }
    }
}

public class TestFactory {
    public static void main(String args[]) {
        Fruit f = Factory.getInstance("apple");
        f.eat();
    }
}

每增加一個(gè)類就要去修改工廠類,那么如果隨時(shí)都可能增加子類呢怠益?工廠類要一直被進(jìn)行修改仪搔。
因?yàn)楝F(xiàn)在工廠類中的對(duì)象都是通過關(guān)鍵字new直接實(shí)例化的,而new就成了所有問題的關(guān)鍵點(diǎn)蜻牢。要想解決這一問題烤咧,就只能夠依靠反射來(lái)完成。

package cn.cc.test;

interface Fruit {
    public void eat();
}

public class Apple implements Fruit {
    @Override
    public void eat() {
        System.out.println("吃蘋果抢呆!");
    }
}

public class Orange implements Fruit {
    @Override
    public void eat() {
        System.out.println("吃橘子煮嫌!");
    }
}

public class Factory {
    public static Fruit getInstance(String className) {
        Fruit f = null;
        try {
            f = (Fruit) Class.forName(className);
        } catch(Exception e) {}
        return f;
    }
}

public class TestFactory {
    public static void main(String args[]) {
        Fruit f = Factory.getInstance("cn.cc.test.Apple");
        f.eat();
    }
}

輸出結(jié)果

吃蘋果!

此時(shí)的程序就真正完成了解耦合的目的抱虐,而且可擴(kuò)展性非常的強(qiáng)昌阿。

使用反射調(diào)用構(gòu)造

在之前 所編寫的代碼實(shí)際上發(fā)現(xiàn)都默認(rèn)使用了類之中的無(wú)參構(gòu)造方法,可是類中還有可能不提供無(wú)參構(gòu)造。

范例:觀察代碼問題
定義Book類:

package cn.cc.po;

public class Book {
    private String title;
    private double price;
    public Book(String title, double price) {
        this.title = title;
        this.price = price;
    }
    @Overide
    public String toString() {
        return "書名:" + this.title + "  價(jià)格:" + this.price;
    }
}

定義TestDemo類:

package cn.cc.demo;

public class TestDemo {
    public static void main(String args[]) {
        Class<?> cls = Class.forName("cn.cc.po.Book");
        Object obj = cls.newInstance();  // 相當(dāng)于使用new調(diào)用無(wú)參構(gòu)造實(shí)例化
        System.out.println(obj);
    }
}

由于此時(shí)Book類沒有提供無(wú)參構(gòu)造方法懦冰,所以代碼會(huì)出錯(cuò)灶轰。

Exception in thread "main" java.lang.InstantiationException: cn.cc.po.Book
    at java.lang.Class.newInstance(Class.java:427)
    at cn.cc.demo.TestDemo.main(TestDemo.java:5)
Caused by: java.lang.NoSuchMethodException: cn.cc.po.Book.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
...

以上所出現(xiàn)的錯(cuò)誤指的就是因?yàn)楫?dāng)前Book類里面并沒有無(wú)參構(gòu)造方法,所以程序無(wú)法進(jìn)行對(duì)象的實(shí)例化刷钢。在這種情況下笋颤,只能夠明確的調(diào)用有參構(gòu)造方法。
在Class類里面提供有一個(gè)方法能夠取得構(gòu)造:

  • 取得全部構(gòu)造:public Constructor<?>[] getConstructors() throws SecurityException内地。
  • 取得一個(gè)指定參數(shù)順序的構(gòu)造: public Constructor<T> getConstructor(Class<?>... parameterTypes)
    throws NoSuchMethodException, SecurityException伴澄。

以上兩個(gè)方法返回的都是“java.lang.reflect.Constructor”類的對(duì)象。在這個(gè)類中提供有一個(gè)明確傳遞有參構(gòu)造內(nèi)容的實(shí)例化對(duì)象的方法:public T newInstance(Object... args) throws InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException阱缓。

范例:明確調(diào)用類中的有參構(gòu)造

package cn.cc.demo;

public class TestDemo {
    public static void main(String args[]) {
        Class<?> cls = Class.forName("cn.cc.po.Book");
        // public Book(String title, double price)非凌,找到類型
        Constructor<?> con = cls.getConstructor(String.class, double.class);
        Object obj = con.newInstance("Java開發(fā)", 79.8);  // 實(shí)例化對(duì)象
        System.out.println(obj);
    }
}

輸出結(jié)果

書名:Java開發(fā)  價(jià)格:79.8

簡(jiǎn)單Java類的開發(fā)之中不管提供有多少個(gè)構(gòu)造方法,請(qǐng)至少保留有無(wú)參構(gòu)造(為反射實(shí)例化對(duì)象的時(shí)候方便)荆针。

反射調(diào)用方法

類中的普通方法只有在一個(gè)類產(chǎn)生實(shí)例化對(duì)象之后才可以調(diào)用敞嗡,并且實(shí)例化對(duì)象的方式有三種:

  • new實(shí)例化對(duì)象。
  • 克隆對(duì)象祭犯。
  • 通過反射實(shí)例化對(duì)象秸妥。

在Class類里面提供有以下取得類中Method(方法)的操作:

  • 取得一個(gè)類中的全部方法:public Method[] getMethods() throws SecurityException。
  • 取得指定方法:public Method getMethod(String name, Class<?>... parameterTypes)
    throws NoSuchMethodException, SecurityException沃粗。

以上的兩個(gè)操作返回的是java.lang.reflect.Method類的對(duì)象粥惧,在這個(gè)類里面重點(diǎn)關(guān)注一個(gè)方法,調(diào)用方法:public native Object invoke(Object receiver, Object... args)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException最盅。

范例:使用反射調(diào)用方法
定義一個(gè)Book類突雪。

package cn.cc.po;

public class Book {
    private String title;
    public void setTitle(String title) {
        this.title = title;
    }
    public String getTitle() {
        return title;
    }
}

定義TestDemo類:

package cn.cc.demo;

public class TestDemo {
    public static void main(String args[]) {
        String fieldName = "title";  // 要操作的成員
        Class<?> cls = Class.forName("cn.cc.po.Book");
        Object obj = cls.newInstance();  // 必須給出實(shí)例化對(duì)象
        Method setMet = cls.getMethod("set" + initcap(fieldName), String.class);
        Method getMet = cls.getMethod("get" + initcap(fieldName);
        setMet.invoke(obj, "Java開發(fā)");  // 等價(jià)于:Book類對(duì)象.setTitle("Java開發(fā)");
        System.out.println(getMet.invoke(obj));  // 等價(jià)于:Book類對(duì)象.getTitle();
    }
    public static String initcap(String str) {
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }
}

輸出結(jié)果

Java開發(fā)

此時(shí)完全看不見具體的操作類型,也就是說(shuō)利用反射可以實(shí)現(xiàn)任意類的指定方法的調(diào)用涡贱。

反射調(diào)用成員

類中的屬性一定要在本類 實(shí)例化對(duì)象產(chǎn)生之后才可以分配內(nèi)存空間咏删。在Class類里面提供有取得成員的方法:

  • 取得全部成員:public Field[] getDeclaredFields() throws SecurityException。
  • 取得指定成員:public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException问词。

返回的類型是java.lang.reflect.Field類督函,在這個(gè)類里面有兩個(gè)重要的方法:

  • 取得屬性內(nèi)容:public native Object get(Object object)
    throws IllegalAccessException, IllegalArgumentException。
  • 設(shè)置屬性內(nèi)容:public native void set(Object object, Object value)
    throws IllegalAccessException, IllegalArgumentException激挪。

一般情況下辰狡,類中的私有屬性,按原始的做法垄分,此時(shí)它一定無(wú)法被外部所使用宛篇。
在java.lang.reflect.AccessibleObjject類下面(JDK1.8之后修改):

  • Executable:下面繼續(xù)繼承了Constructor、Method薄湿。
  • Field叫倍。

在這個(gè)類中提供了一個(gè)方法:public void setAccessible(boolean flag) throws SecurityException偷卧,設(shè)置是否封裝。

范例:使用反射調(diào)用成員
定義一個(gè)Book類吆倦。

package cn.cc.po;

public class Book {
    private String title;
}

定義TestDemo類:

package cn.cc.demo;

public class TestDemo {
    public static void main(String args[]) {
        String fieldName = "title";  // 要操作的成員
        Class<?> cls = Class.forName("cn.cc.po.Book");
        Object obj = cls.newInstance();  // 必須給出實(shí)例化對(duì)象
        Field titleField = cls.getDeclaredField(fieldName);
        titleField.setAccessible(true);  // 封裝取消了饶火,否則不能調(diào)用private屬性
        titleField.set(obj, "Java開發(fā)");  // 等價(jià)于:Book類對(duì)象.title = "Java開發(fā)";
        System.out.println(titleField.get(obj));  // 等價(jià)于:Book類對(duì)象.title;
    }
}

輸出結(jié)果

Java開發(fā)

構(gòu)造方法與普通方法也同樣可以取消封裝焚碌,只不過很少這樣去做擦酌,而且對(duì)于屬性的訪問還是建議使用setter闺鲸、getter方法完成瞻赶。

總結(jié)

  • 實(shí)例化對(duì)象的方式又增加了一種反射赛糟。
  • 對(duì)于簡(jiǎn)單Java類的定義應(yīng)該更加清晰了。
  • 反射調(diào)用類結(jié)構(gòu)只是一個(gè)開始砸逊。

更多內(nèi)容戳這里(整理好的各種文集)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末璧南,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子师逸,更是在濱河造成了極大的恐慌司倚,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件篓像,死亡現(xiàn)場(chǎng)離奇詭異动知,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)员辩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門盒粮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人奠滑,你說(shuō)我怎么就攤上這事丹皱。” “怎么了宋税?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵摊崭,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我杰赛,道長(zhǎng)呢簸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任乏屯,我火速辦了婚禮根时,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瓶珊。我一直安慰自己啸箫,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布伞芹。 她就那樣靜靜地躺著忘苛,像睡著了一般蝉娜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扎唾,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天召川,我揣著相機(jī)與錄音,去河邊找鬼胸遇。 笑死荧呐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的纸镊。 我是一名探鬼主播倍阐,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼逗威!你這毒婦竟也來(lái)了峰搪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤凯旭,失蹤者是張志新(化名)和其女友劉穎概耻,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體罐呼,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鞠柄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嫉柴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厌杜。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖差凹,靈堂內(nèi)的尸體忽然破棺而出期奔,到底是詐尸還是另有隱情,我是刑警寧澤危尿,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布呐萌,位于F島的核電站,受9級(jí)特大地震影響谊娇,放射性物質(zhì)發(fā)生泄漏肺孤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一济欢、第九天 我趴在偏房一處隱蔽的房頂上張望赠堵。 院中可真熱鬧,春花似錦法褥、人聲如沸茫叭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)揍愁。三九已至呐萨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間莽囤,已是汗流浹背谬擦。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留朽缎,地道東北人惨远。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像话肖,于是被迫代替她去往敵國(guó)和親北秽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

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