Java反射-獲取對(duì)象的點(diǎn)點(diǎn)滴滴

文章首發(fā)至個(gè)人公眾號(hào):追風(fēng)棧Binary,歡迎批評(píng)指正

反射的字面含義,除了物理上的意義外筒主,一般理解就是某個(gè)事物所反映出的內(nèi)在性質(zhì)篮迎。Java中也存在這種反射機(jī)制,Wiki中對(duì)于Java反射的定義指的是:在程序運(yùn)行期間可以訪問楼肪、檢測(cè)和修改對(duì)象本身狀態(tài)和行為的能力症革。這種解釋會(huì)在后面進(jìn)行通俗化解釋罗晕。除了面向?qū)ο笄О#琂ava反射也可以說(shuō)是Java的核心理念憔儿。在大型的業(yè)務(wù)代碼中和Github上開源的優(yōu)秀框架代碼中,都可以看到Java反射機(jī)制的影子放可。

首先強(qiáng)推一下JetBrains公司的Java IDE神器IntelliJ IDEA谒臼,從第一次接觸到現(xiàn)在的日常使用中這兩年多,越來(lái)越覺得這款IDE是一種無(wú)與倫比的存在耀里。在IDEA之前是Eclipse蜈缤,而如今:

什么是Java反射?

不通俗的講备韧,Java反射指的是程序代碼在運(yùn)行的過(guò)程中劫樟,對(duì)于程序中任意的類或者對(duì)象,都可以在運(yùn)行時(shí)獲取得到它們的屬性或者方法织堂,包括私有方法和私有變量。通俗的講奶陈,舉個(gè)例子易阳,你的程序中有100個(gè)類,每個(gè)類中都均有100個(gè)成員變量和100個(gè)方法吃粒。但我只需要知道每個(gè)類的名字潦俺,我就可以通過(guò)一種機(jī)制在程序運(yùn)行時(shí)把這些100個(gè)類中的所有成員變量和方法都獲取,并且還可以加以調(diào)用徐勃。這種機(jī)制就是Java反射(Java Reflect)事示。畫了幅圖來(lái)展示這段話的含義。

為什么反射可以做到這些僻肖?

聽上去Java反射就像是一個(gè)魔法肖爵,那么到底是誰(shuí)賦予了它這種能力?這里就不得不引出另一個(gè)Java的內(nèi)容:Java中的Class臀脏。這個(gè)Class如影隨形的伴隨著反射的過(guò)程劝堪,沒有它,反射也立刻失去魔法效力揉稚。

我們?nèi)粘懙腏ava Class文件秒啦,都是對(duì)一類通用共性事物的語(yǔ)言描述,然后通過(guò)這個(gè)類去生成我們所需要的對(duì)象搀玖。那么問題來(lái)了余境,我們寫的Class類是不是對(duì)象?是的,我們平時(shí)寫的Java業(yè)務(wù)類也都是一個(gè)對(duì)象芳来,它是java.lang.Class的對(duì)象暴氏,這意味著每一個(gè)類可以生成自己的對(duì)象之外,自身也還是Class的對(duì)象绣张〈鹩妫可以以一種上帝視角來(lái)看待普通的Java類,那么我們就是那個(gè)上帝Class侥涵,那些產(chǎn)生其它具體對(duì)象的類沼撕,就是我們這個(gè)上帝Class的對(duì)象。查看這個(gè)Class的源碼芜飘,來(lái)窺探其中的一些秘密务豺。

/* java.lang.Class源碼,私有方法嗦明,只有JVM才可以創(chuàng)建Class的對(duì)象
 * Private constructor. Only the Java Virtual Machine creates Class objects.
 * This constructor is not used and prevents the default constructor being
 * generated.
 */
private Class(ClassLoader loader, Class<?> arrayComponentType) {
 // Initialize final field for classLoader.  The initialization value of non-null
 // prevents future JIT optimizations from assuming this final field is null.
 classLoader = loader;
 componentType = arrayComponentType;
}

可以看出這個(gè)Class類的構(gòu)造函數(shù)被private修飾笼沥,是一個(gè)私有方法,并且注釋中也解釋了說(shuō)這個(gè)方法只能由JVM來(lái)調(diào)用創(chuàng)建Class的對(duì)象娶牌。這意味著如下的語(yǔ)句是錯(cuò)誤的奔浅,輸入如下語(yǔ)句會(huì)直接報(bào)錯(cuò)。

雖然我們無(wú)法直接創(chuàng)建上帝這個(gè)自身Class的直接對(duì)象诗良,但是可以通過(guò)其它的方式來(lái)創(chuàng)建汹桦,并且在后面可以看到反射中提及的方法,都是在java.lang.Class這個(gè)類中定義的鉴裹。

Java反射的第一步

我們執(zhí)行反射的目的無(wú)非只有兩個(gè):獲取成員變量或者獲取方法舞骆。Java反射也是基于這兩個(gè)目標(biāo)來(lái)進(jìn)行反射的,但在反射之前径荔,首先需要解決上面留下的問題督禽,怎么獲取Class的對(duì)象?這里先假定我們定義了一個(gè)User類來(lái)討論這個(gè)問題总处。

有三種方法來(lái)執(zhí)行這個(gè)過(guò)程:

  • 直接調(diào)用User類中的隱含靜態(tài)成員變量class狈惫,這個(gè)過(guò)程說(shuō)明class靜態(tài)變量定義在每一個(gè)普通類中,只是我們無(wú)需自行定義辨泳,調(diào)用方式:Class myClass = User.class;

  • 既然有隱含的靜態(tài)變量class虱岂,那么就會(huì)有隱含的getter方法,User類的對(duì)象就可以通過(guò)getter的方法來(lái)獲取Class的對(duì)象

    User user = new User();

    Class myClass = user.getClass();

  • 使用Class類的靜態(tài)方法forName()來(lái)獲取Class對(duì)象菠红,這個(gè)方法接收類的路徑第岖,實(shí)現(xiàn)運(yùn)行時(shí)動(dòng)態(tài)加載。這個(gè)過(guò)程需要捕獲異常试溯,以便在找不到該類時(shí)提供堆棧信息蔑滓。調(diào)用方式:Class myClass = Class.forName(class的路徑);

在完成了Class對(duì)象獲取這一步后,那么就可以開始執(zhí)行反射的后續(xù)階段了,這里先給出示例的父類Person和子類User的代碼键袱,注意其中成員變量以及方法的訪問限制燎窘。并在后面比較了兩種方式獲取的Class對(duì)象。

//這是父類蹄咖,注意訪問限定符
public class Person {
 public String mName;
 public int mAge;
 private String mNickname;
?
 public void say(){
 System.out.println("Hello");
 }
 private void eat(){
 System.out.println("eat something");
 }
}
//這是子類褐健,注意訪問限定符
public class User extends Person{
?
 private String userName;
 public int userAge;
 private String userInfo;
?
 public void showUserInformation(){
 System.out.println("user name is: " + userName);
 System.out.println("user age is: " + userAge);
 }
 private void privateMethod(String str, int num){
 System.out.println(str + num);
 }
 private String getUserName() {
 return userName;
 }
 private void setUserName(String mUserName) {
 this.userName = mUserName;
 }
 private int getUserAge() {
 return userAge;
 }
 private void setUserAge(int mUserAge) {
 this.userAge = mUserAge;
 }
}
//方法1:獲取User類在Class類中的對(duì)象classFirst
User user = new User();
Class classFirst = user.getClass();
//方法2:直接通過(guò)隱含在Person類中的隱含靜態(tài)變量class來(lái)獲取
Class classSecond = User.class;
//查看classFirst和classSecond的類名,是否相同澜汤?
System.out.println("user.getClass() 的輸出結(jié)果: " + classFirst.getName());
System.out.println("User.class的輸出結(jié)果: " + classSecond.getName());

輸出結(jié)果表明蚜迅,獲取Class類對(duì)象的方法是等效的,都輸出了User這個(gè)對(duì)象俊抵,仍然要注意谁不,這個(gè)User對(duì)象的說(shuō)法是針對(duì)Class來(lái)講的。

Java反射--反射成員變量

這一步需要反射出類中的成員變量徽诲,通過(guò)上面的步驟我們得到了Class的對(duì)象User刹帕,那么就可以進(jìn)行反射的操作了。

//classFirst.getFields()獲取子類和父類所有public類型的成員變量
//注意是public標(biāo)明的成員變量,并且返回的是一個(gè)Field數(shù)組
Field[] fieldsFirst = classFirst.getFields();
for(Field field : fieldsFirst){
 System.out.println(field);
}
System.out.println("**************************************");

ClassFirst通過(guò)調(diào)用getFields()這個(gè)方法來(lái)獲取子類和父類(包括Object類)中所有public類型的成員變量谎替,并且保存在Field這個(gè)數(shù)組中偷溺,這句話包含了兩個(gè)信息:

  • 獲取了子類User和父類Person乃至Object類的成員變量

  • 獲取范圍限定在所有的public類型中

并且也解釋了為何反射與Class無(wú)法分割的原因:這個(gè)過(guò)程就是通過(guò)Class的對(duì)象調(diào)用Class的方法來(lái)實(shí)現(xiàn)的。這一步仍有個(gè)局限院喜,如果我想獲取類中的私有變量怎么辦亡蓉?Class類早就為我們考慮好了。

//通過(guò)getDeclaredFields()獲取User類中聲明的變量
//這個(gè)方法得到的變量不會(huì)受到訪問限制符的影響
Field[] fieldsSecond = classFirst.getDeclaredFields();
for(Field field : fieldsSecond){
 System.out.println(field);
}
System.out.println("**************************************");

使用getDeclaredFields()方法便可以獲取Class指向該類這個(gè)對(duì)象的全部成員屬性喷舀,包括privateprotected以及public淋肾,上圖中也看得出硫麻,User類中的成員變量都被打印出來(lái)了。也要注意一點(diǎn):

  • getDeclaredFields()不會(huì)去父類中包含的成員變量樊卓,可以這樣理解拿愧,declared表明已經(jīng)聲明,意味著是指Class當(dāng)前聲明的這個(gè)對(duì)象User碌尔,那么自然就不會(huì)去找父類中的成員變量了浇辜。

那問題是,目前我只是獲得了User類的成員變量唾戚,如果我想獲取Person類的成員變量怎么辦柳洋?
方法很簡(jiǎn)單,創(chuàng)建一個(gè)指向Person的Class對(duì)象就好了叹坦,然后再調(diào)用getDeclaredFields()方法即可得到熊镣。

Java反射--反射方法

反射類中的方法與反射成員變量的做法沒有什么區(qū)別,只是獲取方法數(shù)組的方法名換了,它也存在著getFields()getDeclaredFields()的同樣性質(zhì)绪囱。

//通過(guò)classFirst.getMethods()方法獲取子類與父類中的所有聲明public類型的方法
//注意這個(gè)都會(huì)包括Object類中的方法
Method[] methodsFirst = classFirst.getMethods();
for(Method method : methodsFirst){
 System.out.println(method);
}
System.out.println("**************************************");

圖中可以看到清一色的public修飾的方法测蹲,并且得到了PersonUser類中的兩個(gè)方法,其余的都是Object類中的方法鬼吵,如果想得到User類中所有的方法扣甲,那么和上述的方法思路是一致的。

//同成員變量一致齿椅,getDeclaredMethods()方法會(huì)返回該類User的所有方法
//這就包括private琉挖、protected以及public聲明的方法
Method[] methodsSecond = classFirst.getDeclaredMethods();
for(Method method : methodsSecond){
 System.out.println(method);
}
System.out.println("**************************************");

通過(guò)調(diào)用getDeclaredMethods()便可以得到User類中的所有方法,包括private修飾的方法媒咳。

通過(guò)反射來(lái)修改表達(dá)

我們通過(guò)反射可以獲取到類的成員變量信息和方法信息粹排,如果說(shuō)只是獲取,那意義不大涩澡。不但要能獲取顽耳,還能像屬于自己的方法一樣進(jìn)行修改表達(dá),那么才有意義妙同。通常private修飾的方法或者變量是無(wú)法在外部類正常訪問的射富,但真的無(wú)法訪問嗎?這個(gè)過(guò)程可以通過(guò)反射來(lái)實(shí)現(xiàn)粥帚。

User類中新增了一個(gè)私有的成員變量userInfo胰耗,并為它配備了相應(yīng)的gettersetter,我們通過(guò)反射來(lái)將userInfo這個(gè)變量的值從zhuifeng變?yōu)?code>nanfang芒涡。

System.out.println("**************************************");
System.out.println("修改前的userInfo: " + user.getUserInfo());
Field myField = classFirst.getDeclaredField("userInfo");
if(myField != null){
 //權(quán)限的獲取過(guò)程
 myField.setAccessible(true);
 myField.set(user, "nanfang");
 System.out.println("修改后的userInfo: " + user.getUserInfo());
}
System.out.println("**************************************");</pre>

這個(gè)過(guò)程先是通過(guò)將userInfo這個(gè)變量名作為參數(shù)傳遞給getDeclaredField柴灯,精準(zhǔn)的定位這個(gè)成員變量。然后調(diào)用setAccessible來(lái)獲取其操作權(quán)限费尽,最后通過(guò)set方法將user對(duì)象和修改的值傳入赠群,通過(guò)打印出的結(jié)果可以看出,那個(gè)被private修飾的字符串居然被修改成功了旱幼!

除了修改被private修飾的字符串外查描,被private修飾的方法也難逃厄運(yùn)。

Method method = classFirst.getDeclaredMethod("privateMethod", String.class, int.class);
if(method != null){
 //可以獲取該方法
 method.setAccessible(true);
 //反射得到的方法調(diào)用invoke來(lái)執(zhí)行私有方法
 //第一個(gè)參數(shù)是對(duì)應(yīng)該類的對(duì)象柏卤,后面是該私有方法的參數(shù)
 method.invoke(user, "zhuifeng", 1);
}

與調(diào)用變量的方法使用相似冬三,getDeclaredMethod通過(guò)傳入私有方法的方法名,并將方法參數(shù)對(duì)應(yīng)的類作為參數(shù)進(jìn)行依次傳遞缘缚。在IDEA中當(dāng)使用這個(gè)方法時(shí)勾笆,輸入完該方法的第一個(gè)參數(shù)方法名后,會(huì)自動(dòng)完成后續(xù)的參數(shù)填充忙灼,很方便匠襟。再調(diào)用setAccessible的方法獲取權(quán)限钝侠,就可以調(diào)用invoke方法來(lái)執(zhí)行這個(gè)私有方法,從結(jié)果的輸出來(lái)看酸舍,這個(gè)私有方法的確是被賦值執(zhí)行了帅韧。

Java反射應(yīng)用場(chǎng)景

給定一個(gè)場(chǎng)景:在同一批任務(wù)中,比如說(shuō)有100個(gè)任務(wù)類啃勉,每個(gè)任務(wù)類都定義了10個(gè)同意義但不同表達(dá)的成員變量和方法忽舟。那么如果不加優(yōu)化,我們需要在主方法中把每一個(gè)類的實(shí)現(xiàn)淮阐、對(duì)象成員變量的賦值和方法的調(diào)用全部寫出來(lái)叮阅,這是一個(gè)純粹的體力活,而且效率極其低下泣特。有了反射之后浩姥,只需要寫一個(gè)完整的結(jié)構(gòu)就好了,并且只需要傳入Class引用的某個(gè)具體的類的對(duì)象就可以實(shí)現(xiàn)全部功能状您。在降低了代碼的繁瑣程度同時(shí)勒叠,也增加了程序的擴(kuò)展性,你只需要編寫你自己的類就可以了膏孟,無(wú)需再添加任何代碼眯分,剩下的都由反射來(lái)完成,非常的高效柒桑。

總結(jié)

反射是Java的核心知識(shí)點(diǎn)弊决,但在日常的開發(fā)中,可能用的比較少魁淳。在閱讀一些框架的源碼的時(shí)候飘诗,倒是可以頻繁的看到它們的身影。本文對(duì)Java反射做了一個(gè)大致的介紹界逛,通過(guò)代碼實(shí)現(xiàn)的方式展示反射的作用疚察,拋磚引玉,以求更深領(lǐng)悟的指導(dǎo)仇奶。

參考資料

Java反射由淺入深
淺談Java中的Class類

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市比驻,隨后出現(xiàn)的幾起案子该溯,更是在濱河造成了極大的恐慌,老刑警劉巖别惦,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狈茉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡掸掸,警方通過(guò)查閱死者的電腦和手機(jī)氯庆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門蹭秋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人堤撵,你說(shuō)我怎么就攤上這事仁讨。” “怎么了实昨?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵洞豁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我荒给,道長(zhǎng)丈挟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任志电,我火速辦了婚禮曙咽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘挑辆。我一直安慰自己例朱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布之拨。 她就那樣靜靜地躺著茉继,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蚀乔。 梳的紋絲不亂的頭發(fā)上烁竭,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音吉挣,去河邊找鬼派撕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛睬魂,可吹牛的內(nèi)容都是我干的终吼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼氯哮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼际跪!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起喉钢,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤姆打,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后肠虽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體幔戏,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年税课,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了闲延。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痊剖。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖垒玲,靈堂內(nèi)的尸體忽然破棺而出陆馁,到底是詐尸還是另有隱情,我是刑警寧澤侍匙,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布氮惯,位于F島的核電站,受9級(jí)特大地震影響想暗,放射性物質(zhì)發(fā)生泄漏妇汗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一说莫、第九天 我趴在偏房一處隱蔽的房頂上張望杨箭。 院中可真熱鬧,春花似錦储狭、人聲如沸互婿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)慈参。三九已至,卻和暖如春刮萌,著一層夾襖步出監(jiān)牢的瞬間驮配,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工着茸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留壮锻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓涮阔,卻偏偏與公主長(zhǎng)得像猜绣,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子敬特,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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