文章首發(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ì)象的全部成員屬性喷舀,包括private
、protected
以及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
修飾的方法测蹲,并且得到了Person
和User
類中的兩個(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)的getter
和setter
,我們通過(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)仇奶。