Java SE基礎(chǔ)鞏固(七):反射

我第一次聽說反射這個概念是在《Java編程思想》中看到的,說起這書我有些憂傷愧薛,當(dāng)時自學(xué)Java,沒有前輩指導(dǎo)衫画,自己摸著石子過河毫炉,隨便網(wǎng)上搜一下入門書籍,竟然清一色的推薦《Java編程思想》(當(dāng)時大概2016年初削罩,也許只是我當(dāng)時知識辨別能力比較低的原因)欣舵,現(xiàn)在看來荆永,該書確實不適合入門憋他,比較適合有一定開發(fā)經(jīng)驗的開發(fā)者。有點(diǎn)扯遠(yuǎn)了愿阐,拉回來,拉回來趾疚。

1 RTTI和反射

在《Java編程思想》中提到反射的時候缨历,作者將其看做是Java的RTTI,RTTI即Run Time Type Infomation(運(yùn)行時類型信息)糙麦,但實際上RTTI可是說是特指C++的RTTI辛孵,Java是沒有這個概念的,也許只是作者考慮到C++讀者比較多的原因吧赡磅。

1.1 RTTI

RTTI是C++語言的核心機(jī)制魄缚,它允許程序在運(yùn)行時動態(tài)的決定各個對象的類型,例如經(jīng)常使用到的dynamic_cast焚廊,該語法可以將某個對象在運(yùn)行時動態(tài)的轉(zhuǎn)換成其他任意類型冶匹,但之后是否會發(fā)生錯誤,就不歸它管了咆瘟。

1.2 反射

反射也不是Java語言獨(dú)有的概念嚼隘,而是計算機(jī)科學(xué)的通用概念,在維基百科上有如下解釋:

計算機(jī)科學(xué)中搞疗,反射是指計算機(jī)程序運(yùn)行時(Run time)可以訪問嗓蘑、檢測和修改它本身狀態(tài)或行為的一種能力。用比喻來說匿乃,反射就是程序在運(yùn)行的時候能夠“觀察”并且修改自己的行為桩皿。

要注意術(shù)語“反射”和“內(nèi)省“type introspection)的關(guān)系。內(nèi)蚀闭ā(或稱“自省”)機(jī)制僅指程序在運(yùn)行時對自身信息(稱為元數(shù)據(jù))的檢測泄隔;反射機(jī)制不僅包括要能在運(yùn)行時對程序自身信息進(jìn)行檢測,還要求程序能進(jìn)一步根據(jù)這些信息改變程序狀態(tài)或結(jié)構(gòu)宛徊。

從上面描述來看佛嬉,反射和RTTI確實很像,但我更傾向于將RTTI認(rèn)為是反射的子集闸天,反射包含的范圍應(yīng)該更廣暖呕,不僅可以動態(tài)的轉(zhuǎn)換類型,還可以在運(yùn)行時對對象的行為(方法)苞氮,狀態(tài)(字段)做訪問湾揽、修改等操作。

之所以要談到RTTI,是因為如果僅僅通過《Java編程思想》了解反射库物,可能會對作者的意思理解不深刻霸旗,會認(rèn)為反射等同于C++ 的RTTI。(我當(dāng)初就是這樣)

2 Java反射

具體到Java中的反射戚揭,可以這樣解釋:Java反射機(jī)制讓我們可以在運(yùn)行時訪問任何一個類的元信息诱告,包括其接口,父類民晒,字段精居,方法等。JDK還提供了API讓我們可以方便使用反射機(jī)制镀虐,這些API都在java.lang.reflect包下箱蟆,這些API包括Method,F(xiàn)ield刮便,Array等空猜。不夸張的說,熟悉反射真的可以在Java世界里“為所欲為”恨旱,很多框架非常依賴反射技術(shù)辈毯,例如Spring,MyBaties等搜贤,Spring會在運(yùn)行時獲取類谆沃、方法、字段上的注解信息仪芒,然后對其做對應(yīng)的處理唁影。

2.1 類對象

類對象即Class對象,所有的類都有一個Class對象引用掂名,該引用指向方法區(qū)中對應(yīng)類的類信息据沈,該引用在虛擬機(jī)規(guī)范中是有規(guī)定的,所以無論哪種虛擬機(jī)實現(xiàn)都一定會有這么一個Class對象引用饺蔑,甚至基本類型都會有锌介。例如:

public class Main {

    public static void main(String[] args) {
        //直接使用類型名.class的方式獲取
        Class<?> intClass = int.class;
        Class<?> userClass = User.class;
        User user = new User();
        //使用對象引用.getClass()的方式獲取
        Class<?> userClass2 = user.getClass();
        //對于基本類型,名字就是類型名猾警,例如int類型的name就是int
        System.out.println(intClass.getName());
        //對于類來說孔祸,名字是全限定類名
        System.out.println(userClass.getName());
        System.out.println(userClass2.getName());

        //simpleName是將包的信息略掉,只有類名
        System.out.println(userClass.getSimpleName());
    }
}

上面代碼用兩種方式獲取類對象发皿,一種是直接使用類型名.class崔慧,一種是使用對象引用調(diào)用getClass()方法,基本類型只能使用第一種方法穴墅,引用類型兩種方法都可以使用惶室,拿到類對象的引用之后就可以“為所欲為”了匣屡!可以獲取到該類有幾個方法,分別是什么方法拇涤,其方法簽名是怎樣的等等信息,下面的代碼演示了反射的簡單使用:

2.2 對字段進(jìn)行操作

Field類有各種對字段進(jìn)行操作的API誉结,而獲取Field對象則需要先獲取類對象鹅士,然后通過調(diào)用getDeclaredFields()或者getFields()來獲取Field數(shù)組,其中g(shù)etDeclaredFields()方法會包括私有字段惩坑,而getFields()不包括掉盅,還可以通過調(diào)用getField(String)或者getDeclaredField(String)方法來獲取指定名字的字段,如果找不到就會拋出NoSuchFieldException異常以舒。下面的代碼演示了如何操作字段:

public class Main {

    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
        User user = new User();
        Class<?> userClass = user.getClass();

        //獲取字段
        Field[] fields = userClass.getDeclaredFields();
        for (Field field : fields) {
            System.out.println("field Type is " + field.getType().getName() + " ---- field name is " + field.getName());
        }
        System.out.println("before set field value : " + user.getId());
        Field field = userClass.getDeclaredField("id");
        field.setAccessible(true);
        field.set(user, 314L);
        System.out.println("after set filed value : " + user.getId());
    }
}

代碼中先獲取了字段數(shù)組趾痘,該數(shù)組包含了該類聲明的所有字段,通過Field對象蔓钟,我們可以獲取對象名字永票,類型,甚至該字段在某個對象中的值滥沫。之后通過getDeclaredField("id")獲取了名為id的的字段侣集,并設(shè)置其可訪問性為true,如果該字段是私有字段兰绣,不設(shè)置訪問性為true的話世分,將無法訪問該字段,緊接著使用set方法設(shè)置該字段的值缀辩,set方法有兩個參數(shù)臭埋,第一個參數(shù)是要作用的對象實例,第二個參數(shù)是字段的值臀玄,下面是該程序運(yùn)行的結(jié)果:

field Type is java.lang.Long ---- field name is id
field Type is java.lang.String ---- field name is username
field Type is java.lang.String ---- field name is password
before set field value : null
after set filed value : 314

除此之外瓢阴,還可以獲取字段的注解、其父類等信息镐牺,Spring 框架的IOC容器有自動裝配的功能炫掐,可以自動對字段進(jìn)行賦值,該功能的實現(xiàn)原理就是依賴反射睬涧,運(yùn)行時獲取字段的類型信息募胃,注解信息(用來判斷是否要進(jìn)行自動裝配),然后在容器中查找該類的實例畦浓,查到就直接對其賦值痹束,查不到就拋出異常。

2.3 對方法進(jìn)行操作

Method類也有很多對方法進(jìn)行操作的API讶请,不過大多數(shù)都是獲取方法的信息祷嘶,例如方法的返回值屎媳,參數(shù)列表,參數(shù)個數(shù)论巍,方法名等烛谊,幾乎沒有修改方法的API。其API的命名和Filed的極為相似嘉汰,可以說是用的同一種命名模式丹禀。下面的代碼演示了如何對方法進(jìn)行操作:

public class Main {

    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
        User user = new User();
        Class<?> userClass = user.getClass();

        //獲取方法
        Method[] methods = userClass.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println("method return type is " + method.getReturnType());
            System.out.println("method name is " + method.getName());
            System.out.println("method params count " + method.getParameterCount());
            Parameter[] parameters = method.getParameters();
            for (Parameter parameter : parameters) {
                System.out.println("param type is " + parameter.getType());
                System.out.println("param name is " + parameter.getName());
            }
            System.out.println("----------------------------------------");
        }
        Method testMethod1 = userClass.getMethod("testMethod1", int.class, int.class);
        testMethod1.setAccessible(true);
        testMethod1.invoke(user, 1,1);  //調(diào)用該方法

    }
}

和Filed一樣,先通過getDeclaredMethods()獲取所有方法鞋怀,每個方法都是一個Method對象實例双泪,這只是JDK API對其進(jìn)行的抽象,實際上在虛擬機(jī)中并沒有那么簡單密似,然后通過各種API來獲取信息焙矛,在代碼中獲取了方法的返回值,名字残腌,參數(shù)各種以及其參數(shù)列表村斟,同時遍歷了其參數(shù)列表。最后通過getMethod()指定相關(guān)參數(shù)獲取了指定的方法對象實例抛猫,getMethod()的第一個參數(shù)是方法名邓梅,第二個參數(shù)是幾個可變參數(shù),表示參數(shù)的類對象邑滨,我定義的testMethod1只有兩個int參數(shù)日缨,所以這里傳入了兩個int.class對象,如果沒有找到對應(yīng)的方法掖看,就拋出NoSuchMethodException異常匣距。

隨后將其設(shè)置成可訪問的,并使用invoke調(diào)用該方法哎壳,invoke的第一個參數(shù)是要作用的對象實例毅待,第二個參數(shù)也是一個可變參數(shù),需要傳入的是參數(shù)的值归榕。最后將程序運(yùn)行尸红,大致可以看到如下輸出:

....
method type is void
method name is setPassword
method params count 1
param type is class java.lang.String
param name is arg0
----------------------------------------
method type is void
method name is testMethod1
method params count 2
param type is int
param name is arg0
param type is int
param name is arg1
----------------------------------------
....

其實還有更多,我這里只是截取了部分刹泄。輸出大部分內(nèi)容符合我們預(yù)期外里,但參數(shù)名輸出的東西是什么鬼?arg0特石、arg1是個什么東西盅蝗?

我們一直在說反射是運(yùn)行時的一種機(jī)制,即操作的對象是編譯后的字節(jié)碼姆蘸,java8之前方法的參數(shù)名在編譯之后會被類似arg0墩莫,arg1代替芙委,java8之后提供了一個-parameters 編譯選項,該選擇默認(rèn)是關(guān)閉的狂秦,指定之后才會打開灌侣,打開情況下,編譯后的字節(jié)碼就會使用源碼的參數(shù)名稱了裂问。那在此之前顶瞳,有什么辦法運(yùn)行時獲取字段名稱呢?答案是使用ASM等字節(jié)碼技術(shù)愕秫,關(guān)于該技術(shù)的使用,本文不會涉及焰络,有興趣的朋友可以到網(wǎng)上搜索相關(guān)資料戴甩。

3 小結(jié)

反射也是一項博大精深的技術(shù),本文僅僅是簡單的介紹了反射的簡單使用闪彼,關(guān)于其更多的使用其實和操作字段甜孤、方法差不多,一通百通即可畏腕,實在不行再看看JDK文檔就肯定會了缴川。關(guān)于其原理,本文沒涉及描馅,原因是如果讀者對虛擬機(jī)有一定了解的話把夸,不難猜到其原理,其實這些什么字段铭污、方法恋日、注解、接口等信息在類加載完成之后會被存儲在方法區(qū)里嘹狞,同時還留了一個Class對象的引用用于訪問這些信息岂膳。

很多框架都或多或少的使用到反射,實在是因為反射非常適合做這種“幕后”的事磅网。最后谈截,學(xué)好反射真的可以在Java世界里“為所欲為”。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末涧偷,一起剝皮案震驚了整個濱河市簸喂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌燎潮,老刑警劉巖娘赴,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異跟啤,居然都是意外死亡诽表,警方通過查閱死者的電腦和手機(jī)唉锌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來竿奏,“玉大人袄简,你說我怎么就攤上這事》盒ィ” “怎么了绿语?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長候址。 經(jīng)常有香客問我吕粹,道長,這世上最難降的妖魔是什么岗仑? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任匹耕,我火速辦了婚禮,結(jié)果婚禮上荠雕,老公的妹妹穿的比我還像新娘稳其。我一直安慰自己,他們只是感情好炸卑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布既鞠。 她就那樣靜靜地躺著,像睡著了一般盖文。 火紅的嫁衣襯著肌膚如雪嘱蛋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天五续,我揣著相機(jī)與錄音浑槽,去河邊找鬼。 笑死返帕,一個胖子當(dāng)著我的面吹牛桐玻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播荆萤,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼镊靴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了链韭?” 一聲冷哼從身側(cè)響起偏竟,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎敞峭,沒想到半個月后踊谋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡旋讹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年殖蚕,在試婚紗的時候發(fā)現(xiàn)自己被綠了轿衔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡睦疫,死狀恐怖害驹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蛤育,我是刑警寧澤宛官,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站瓦糕,受9級特大地震影響底洗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咕娄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一亥揖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谭胚,春花似錦、人聲如沸未玻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扳剿。三九已至旁趟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間庇绽,已是汗流浹背锡搜。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瞧掺,地道東北人耕餐。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像辟狈,于是被迫代替她去往敵國和親肠缔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355

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