java 反射 int_Java 反射由浅入深 | 进阶必备
原標(biāo)題:Java 反射由淺入深 | 進(jìn)階必備
一、Java 反射機(jī)制
參考了許多博文,總結(jié)了以下個(gè)人觀點(diǎn),若有不妥還望指正:
Java 反射機(jī)制在程序運(yùn)行時(shí),對(duì)于任意一個(gè)類(lèi),都能夠知道這個(gè)類(lèi)的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法和屬性。這種 動(dòng)態(tài)的獲取信息 以及 動(dòng)態(tài)調(diào)用對(duì)象的方法 的功能稱(chēng)為 java 的反射機(jī)制。
反射機(jī)制很重要的一點(diǎn)就是“運(yùn)行時(shí)”,其使得我們可以在程序運(yùn)行時(shí)加載、探索以及使用編譯期間完全未知的 .class 文件。換句話(huà)說(shuō),Java 程序可以加載一個(gè)運(yùn)行時(shí)才得知名稱(chēng)的 .class 文件,然后獲悉其完整構(gòu)造,并生成其對(duì)象實(shí)體、或?qū)ζ?fields(變量)設(shè)值、或調(diào)用其 methods(方法)。
不知道上面的理論你能否明白,反正剛接觸反射時(shí)我一臉懵比,后來(lái)寫(xiě)了幾個(gè)例子之后:哦~~原來(lái)是這個(gè)意思!
若暫時(shí)不明白理論沒(méi)關(guān)系,先往下看例子,之后再回來(lái)看相信你就能明白了。
二、使用反射獲取類(lèi)的信息
為使得測(cè)試結(jié)果更加明顯,我首先定義了一個(gè) FatherClass 類(lèi)(默認(rèn)繼承自 Object 類(lèi)),然后定義一個(gè)繼承自 FatherClass 類(lèi)的 SonClass 類(lèi),如下所示??梢钥吹綔y(cè)試類(lèi)中變量以及方法的訪(fǎng)問(wèn)權(quán)限不是很規(guī)范,是為了更明顯得查看測(cè)試結(jié)果而故意設(shè)置的,實(shí)際項(xiàng)目中不提倡這么寫(xiě)。
FatherClass.java
public class FatherClass {
public String mFatherName;
public int mFatherAge;
public void printFatherMsg(){}
}
SonClass.java
public class SonClass extends FatherClass{
private String mSonName; protected int mSonAge; public String mSonBirthday; public void printSonMsg(){ System.out.println("Son Msg - name : " + mSonName + "; age : " + mSonAge); } private void setSonName(String name){ mSonName = name; } private void setSonAge(int age){ mSonAge = age; } private int getSonAge(){ return mSonAge; } private String getSonName(){ return mSonName; }
}
1. 獲取類(lèi)的所有變量信息
/**
* 通過(guò)反射獲取類(lèi)的所有變量
*/
private static void printFields(){
//1.獲取并輸出類(lèi)的名稱(chēng)
Class mClass = SonClass.class;
System.out.println(“類(lèi)的名稱(chēng):” + mClass.getName());
//2.1 獲取所有 public 訪(fǎng)問(wèn)權(quán)限的變量 // 包括本類(lèi)聲明的和從父類(lèi)繼承的 Field[] fields = mClass.getFields(); //2.2 獲取所有本類(lèi)聲明的變量(不問(wèn)訪(fǎng)問(wèn)權(quán)限) //Field[] fields = mClass.getDeclaredFields(); //3. 遍歷變量并輸出變量信息 for (Field field : fields) { //獲取訪(fǎng)問(wèn)權(quán)限并輸出 int modifiers = field.getModifiers(); System.out.print(Modifier.toString(modifiers) + " "); //輸出變量的類(lèi)型及變量名 System.out.println(field.getType().getName() + " " + field.getName()); }
}
以上代碼注釋很詳細(xì),就不再解釋了。需要注意的是注釋中 2.1 的 getFields() 與 2.2的 getDeclaredFields() 之間的區(qū)別,下面分別看一下兩種情況下的輸出??粗皬?qiáng)調(diào)一下:
SonClass extends FatherClass extends Object :
調(diào)用 getFields() 方法,輸出 SonClass 類(lèi)以及其所繼承的父類(lèi)( 包括 FatherClass 和 Object ) 的 public 方法。注:Object 類(lèi)中沒(méi)有成員變量,所以沒(méi)有輸出。
類(lèi)的名稱(chēng):obj.SonClass
public java.lang.String mSonBirthday
public java.lang.String mFatherName
public int mFatherAge
調(diào)用 getDeclaredFields() , 輸出 SonClass 類(lèi)的所有成員變量,不問(wèn)訪(fǎng)問(wèn)權(quán)限。
類(lèi)的名稱(chēng):obj.SonClass
private java.lang.String mSonName
protected int mSonAge
public java.lang.String mSonBirthday
2. 獲取類(lèi)的所有方法信息
/**
* 通過(guò)反射獲取類(lèi)的所有方法
*/
private static void printMethods(){
//1.獲取并輸出類(lèi)的名稱(chēng)
Class mClass = SonClass.class;
System.out.println(“類(lèi)的名稱(chēng):” + mClass.getName());
//2.1 獲取所有 public 訪(fǎng)問(wèn)權(quán)限的方法 //包括自己聲明和從父類(lèi)繼承的 Method[] mMethods = mClass.getMethods(); //2.2 獲取所有本類(lèi)的的方法(不問(wèn)訪(fǎng)問(wèn)權(quán)限) //Method[] mMethods = mClass.getDeclaredMethods(); //3.遍歷所有方法 for (Method method : mMethods) { //獲取并輸出方法的訪(fǎng)問(wèn)權(quán)限(Modifiers:修飾符) int modifiers = method.getModifiers(); System.out.print(Modifier.toString(modifiers) + " "); //獲取并輸出方法的返回值類(lèi)型 Class returnType = method.getReturnType(); System.out.print(returnType.getName() + " " + method.getName() + "( "); //獲取并輸出方法的所有參數(shù) Parameter[] parameters = method.getParameters(); for (Parameter parameter: parameters) { System.out.print(parameter.getType().getName() + " " + parameter.getName() + ","); } //獲取并輸出方法拋出的異常 Class[] exceptionTypes = method.getExceptionTypes(); if (exceptionTypes.length == 0){ System.out.println(" )"); } else { for (Class c : exceptionTypes) { System.out.println(" ) throws " + c.getName()); } } }
}
同獲取變量信息一樣,需要注意注釋中 2.1 與 2.2 的區(qū)別,下面看一下打印輸出:
調(diào)用 getMethods() 方法
獲取 SonClass 類(lèi)所有 public 訪(fǎng)問(wèn)權(quán)限的方法,包括從父類(lèi)繼承的。打印信息中,printSonMsg() 方法來(lái)自 SonClass 類(lèi), printFatherMsg() 來(lái)自 FatherClass 類(lèi),其余方法來(lái)自 Object 類(lèi)。
類(lèi)的名稱(chēng):obj.SonClass
public void printSonMsg( )
public void printFatherMsg( )
public final void wait( ) throws java.lang.InterruptedException
public final void wait( long arg0,int arg1, ) throws java.lang.InterruptedException
public final native void wait( long arg0, ) throws java.lang.InterruptedException
public boolean equals( java.lang.Object arg0, )
public java.lang.String toString( )
public native int hashCode( )
public final native java.lang.Class getClass( )
public final native void notify( )
public final native void notifyAll( )
調(diào)用 getDeclaredMethods() 方法
打印信息中,輸出的都是 SonClass 類(lèi)的方法,不問(wèn)訪(fǎng)問(wèn)權(quán)限。
類(lèi)的名稱(chēng):obj.SonClass
private int getSonAge( )
private void setSonAge( int arg0, )
public void printSonMsg( )
private void setSonName( java.lang.String arg0, )
private java.lang.String getSonName( )
三、訪(fǎng)問(wèn)或操作類(lèi)的私有變量和方法
在上面,我們成功獲取了類(lèi)的變量和方法信息,驗(yàn)證了在運(yùn)行時(shí) 動(dòng)態(tài)的獲取信息 的觀點(diǎn)。那么,僅僅是獲取信息嗎?我們接著往后看。
都知道,對(duì)象是無(wú)法訪(fǎng)問(wèn)或操作類(lèi)的私有變量和方法的,但是,通過(guò)反射,我們就可以做到。沒(méi)錯(cuò),反射可以做到!下面,讓我們一起探討如何利用反射訪(fǎng)問(wèn) 類(lèi)對(duì)象的私有方法 以及修改 私有變量或常量。
老規(guī)矩,先上測(cè)試類(lèi)。
注:
請(qǐng)注意看測(cè)試類(lèi)中變量和方法的修飾符(訪(fǎng)問(wèn)權(quán)限);
測(cè)試類(lèi)僅供測(cè)試,不提倡實(shí)際開(kāi)發(fā)時(shí)這么寫(xiě) : )
TestClass.java
public class TestClass {
private String MSG = "Original"; private void privateMethod(String head , int tail){ System.out.print(head + tail); } public String getMsg(){ return MSG; }
}
3.1 訪(fǎng)問(wèn)私有方法
以訪(fǎng)問(wèn) TestClass 類(lèi)中的私有方法 privateMethod(…) 為例,方法加參數(shù)是為了考慮最全的情況,很貼心有木有?先貼代碼,看注釋,最后我會(huì)重點(diǎn)解釋部分代碼。
/**
* 訪(fǎng)問(wèn)對(duì)象的私有方法
* 為簡(jiǎn)潔代碼,在方法上拋出總的異常,實(shí)際開(kāi)發(fā)別這樣
*/
private static void getPrivateMethod() throws Exception{
//1. 獲取 Class 類(lèi)實(shí)例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();
//2. 獲取私有方法 //第一個(gè)參數(shù)為要獲取的私有方法的名稱(chēng) //第二個(gè)為要獲取方法的參數(shù)的類(lèi)型,參數(shù)為 Class...,沒(méi)有參數(shù)就是null //方法參數(shù)也可這么寫(xiě) :new Class[]{String.class , int.class} Method privateMethod = mClass.getDeclaredMethod("privateMethod", String.class, int.class); //3. 開(kāi)始操作方法 if (privateMethod != null) { //獲取私有方法的訪(fǎng)問(wèn)權(quán) //只是獲取訪(fǎng)問(wèn)權(quán),并不是修改實(shí)際權(quán)限 privateMethod.setAccessible(true); //使用 invoke 反射調(diào)用私有方法 //privateMethod 是獲取到的私有方法 //testClass 要操作的對(duì)象 //后面兩個(gè)參數(shù)傳實(shí)參 privateMethod.invoke(testClass, "Java Reflect ", 666); }
}
需要注意的是,第3步中的 setAccessible(true) 方法,是獲取私有方法的訪(fǎng)問(wèn)權(quán)限,如果不加會(huì)報(bào)異常 IllegalAccessException,因?yàn)楫?dāng)前方法訪(fǎng)問(wèn)權(quán)限是“private”的,如下:
java.lang.IllegalAccessException: Class MainClass can not access a member of class obj.TestClass with modifiers “private”
正常運(yùn)行后,打印如下,調(diào)用私有方法成功:
Java Reflect 666
3.2 修改私有變量
以修改 TestClass 類(lèi)中的私有變量 MSG 為例,其初始值為 “Original” ,我們要修改為 “Modified”。老規(guī)矩,先上代碼看注釋。
/**
* 修改對(duì)象私有變量的值
* 為簡(jiǎn)潔代碼,在方法上拋出總的異常
*/
private static void modifyPrivateFiled() throws Exception {
//1. 獲取 Class 類(lèi)實(shí)例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();
//2. 獲取私有變量 Field privateField = mClass.getDeclaredField("MSG"); //3. 操作私有變量 if (privateField != null) { //獲取私有變量的訪(fǎng)問(wèn)權(quán) privateField.setAccessible(true); //修改私有變量,并輸出以測(cè)試 System.out.println("Before Modify:MSG = " + testClass.getMsg()); //調(diào)用 set(object , value) 修改變量的值 //privateField 是獲取到的私有變量 //testClass 要操作的對(duì)象 //"Modified" 為要修改成的值 privateField.set(testClass, "Modified"); System.out.println("After Modify:MSG = " + testClass.getMsg()); }
}
此處代碼和訪(fǎng)問(wèn)私有方法的邏輯差不多,就不再贅述,從輸出信息看出 修改私有變量 成功:
Before Modify:MSG = Original
After Modify:MSG = Modified
3.3 修改私有常量
在 3.2 中,我們介紹了如何修改私有 變量,現(xiàn)在來(lái)說(shuō)說(shuō)如何修改私有 常量,
真的能修改嗎?
常量是指使用 final 修飾符修飾的成員屬性,與變量的區(qū)別就在于有無(wú) final 關(guān)鍵字修飾。在說(shuō)之前,先補(bǔ)充一個(gè)知識(shí)點(diǎn)。
Java 虛擬機(jī)(JVM)在編譯 .java 文件得到 .class 文件時(shí),會(huì)優(yōu)化我們的代碼以提升效率。其中一個(gè)優(yōu)化就是:JVM 在編譯階段會(huì)把引用常量的代碼替換成具體的常量值,如下所示(部分代碼)。
編譯前的 .java 文件:
//注意是 String 類(lèi)型的值
private final String FINAL_VALUE = “hello”;
if(FINAL_VALUE.equals(“world”)){
//do something
}
編譯后得到的 .class 文件(當(dāng)然,編譯后是沒(méi)有注釋的):
private final String FINAL_VALUE = “hello”;
//替換為”hello”
if(“hello”.equals(“world”)){
//do something
}
但是,并不是所有常量都會(huì)優(yōu)化。經(jīng)測(cè)試對(duì)于 int 、long 、boolean 以及 String 這些基本類(lèi)型 JVM 會(huì)優(yōu)化,而對(duì)于 Integer 、Long 、Boolean 這種包裝類(lèi)型,或者其他諸如 Date 、Object 類(lèi)型則不會(huì)被優(yōu)化。
總結(jié)來(lái)說(shuō):對(duì)于基本類(lèi)型的靜態(tài)常量,JVM 在編譯階段會(huì)把引用此常量的代碼替換成具體的常量值。
這么說(shuō)來(lái),在實(shí)際開(kāi)發(fā)中,如果我們想修改某個(gè)類(lèi)的常量值,恰好那個(gè)常量是基本類(lèi)型的,豈不是無(wú)能為力了?反正我個(gè)人認(rèn)為除非修改源碼,否則真沒(méi)辦法!
這里所謂的無(wú)能為力是指:我們?cè)诔绦蜻\(yùn)行時(shí)刻依然可以使用反射修改常量的值(后面會(huì)代碼驗(yàn)證),但是 JVM 在編譯階段得到的 .class 文件已經(jīng)將常量?jī)?yōu)化為具體的值,在運(yùn)行階段就直接使用具體的值了,所以即使修改了常量的值也已經(jīng)毫無(wú)意義了。
下面我們驗(yàn)證這一點(diǎn),在測(cè)試類(lèi) TestClass 類(lèi)中添加如下代碼:
//String 會(huì)被 JVM 優(yōu)化
private final String FINAL_VALUE = “FINAL”;
public String getFinalValue(){
//劇透,會(huì)被優(yōu)化為: return “FINAL” ,拭目以待吧
return FINAL_VALUE;
}
接下來(lái),是修改常量的值,先上代碼,請(qǐng)仔細(xì)看注釋:
/**
* 修改對(duì)象私有常量的值
* 為簡(jiǎn)潔代碼,在方法上拋出總的異常,實(shí)際開(kāi)發(fā)別這樣
*/
private static void modifyFinalFiled() throws Exception {
//1. 獲取 Class 類(lèi)實(shí)例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();
//2. 獲取私有常量 Field finalField = mClass.getDeclaredField("FINAL_VALUE"); //3. 修改常量的值 if (finalField != null) { //獲取私有常量的訪(fǎng)問(wèn)權(quán) finalField.setAccessible(true); //調(diào)用 finalField 的 getter 方法 //輸出 FINAL_VALUE 修改前的值 System.out.println("Before Modify:FINAL_VALUE = " + finalField.get(testClass)); //修改私有常量 finalField.set(testClass, "Modified"); //調(diào)用 finalField 的 getter 方法 //輸出 FINAL_VALUE 修改后的值 System.out.println("After Modify:FINAL_VALUE = " + finalField.get(testClass)); //使用對(duì)象調(diào)用類(lèi)的 getter 方法 //獲取值并輸出 System.out.println("Actually :FINAL_VALUE = " + testClass.getFinalValue()); }
}
上面的代碼不解釋了,注釋巨詳細(xì)有木有!特別注意一下第3步的注釋,然后來(lái)看看輸出,已經(jīng)迫不及待了,擦亮雙眼:
Before Modify:FINAL_VALUE = FINAL
After Modify:FINAL_VALUE = Modified
Actually :FINAL_VALUE = FINAL
結(jié)果出來(lái)了:
第一句打印修改前 FINAL_VALUE 的值,沒(méi)有異議;
第二句打印修改后常量的值,說(shuō)明FINAL_VALUE確實(shí)通過(guò)反射修改了;
第三句打印通過(guò) getFinalValue() 方法獲取的 FINAL_VALUE 的值,但還是初始值,導(dǎo)致修改無(wú)效!
這結(jié)果你覺(jué)得可信嗎?什么,你還不信?問(wèn)我怎么知道 JVM 編譯后會(huì)優(yōu)化代碼?那要不這樣吧,一起來(lái)看看 TestClass.java 文件編譯后得到的 TestClass.class 文件。為避免說(shuō)代碼是我自己手寫(xiě)的,我決定不粘貼代碼,直接截圖:
看到了吧,有圖有真相,getFinalValue() 方法直接 return “FINAL”!同時(shí)也說(shuō)明了,程序運(yùn)行時(shí)是根據(jù)編譯后的 .class 來(lái)執(zhí)行的。
順便提一下,如果你有時(shí)間,可以換幾個(gè)數(shù)據(jù)類(lèi)型試試,正如上面說(shuō)的,有些數(shù)據(jù)類(lèi)型是不會(huì)優(yōu)化的。你可以修改數(shù)據(jù)類(lèi)型后,根據(jù)我的思路試試,看輸出覺(jué)得不靠譜就直接看 .classs 文件,一眼就能看出來(lái)哪些數(shù)據(jù)類(lèi)型優(yōu)化了 ,哪些沒(méi)有優(yōu)化。下面說(shuō)下一個(gè)知識(shí)點(diǎn)。
想辦法也要修改!
不能修改,這你能忍?別著急,不知你發(fā)現(xiàn)沒(méi),剛才的常量都是在聲明時(shí)就直接賦值了。你可能會(huì)疑惑,常量不都是在聲明時(shí)賦值嗎?不賦值不報(bào)錯(cuò)?當(dāng)然不是啦。
方法一
事實(shí)上,Java 允許我們聲明常量時(shí)不賦值,但必須在構(gòu)造函數(shù)中賦值。你可能會(huì)問(wèn)我為什么要說(shuō)這個(gè),這就解釋:
我們修改一下 TestClass 類(lèi),在聲明常量時(shí)不賦值,然后添加構(gòu)造函數(shù)并為其賦值,大概看一下修改后的代碼(部分代碼 ):
public class TestClass {
//...... private final String FINAL_VALUE; //構(gòu)造函數(shù)內(nèi)為常量賦值 public TestClass(){ this.FINAL_VALUE = "FINAL"; } //......
}
現(xiàn)在,我們?cè)僬{(diào)用上面貼出的修改常量的方法,發(fā)現(xiàn)輸出是這樣的:
Before Modify:FINAL_VALUE = FINAL
After Modify:FINAL_VALUE = Modified
Actually :FINAL_VALUE = Modified
納尼,最后一句輸出修改后的值了?對(duì),修改成功了!想知道為啥,還得看編譯后的 TestClass.class 文件的貼圖,圖中有標(biāo)注。
解釋一下:我們將賦值放在構(gòu)造函數(shù)中,構(gòu)造函數(shù)是我們運(yùn)行時(shí) new 對(duì)象才會(huì)調(diào)用的,所以就不會(huì)像之前直接為常量賦值那樣,在編譯階段將 getFinalValue() 方法優(yōu)化為返回常量值,而是指向 FINAL_VALUE ,這樣我們?cè)谶\(yùn)行階段通過(guò)反射修改敞亮的值就有意義啦。但是,看得出來(lái),程序還是有優(yōu)化的,將構(gòu)造函數(shù)中的賦值語(yǔ)句優(yōu)化了。再想想那句 程序運(yùn)行時(shí)是根據(jù)編譯后的 .class 來(lái)執(zhí)行的 ,相信你一定明白為什么這么輸出了!
方法二
請(qǐng)你務(wù)必將上面捋清楚了再往下看。接下來(lái)再說(shuō)一種改法,不使用構(gòu)造函數(shù),也可以成功修改常量的值,但原理上都一樣。去掉構(gòu)造函數(shù),將聲明常量的語(yǔ)句改為使用三目表達(dá)式賦值:
private final String FINAL_VALUE
= null == null ? “FINAL” : null;
其實(shí),上述代碼等價(jià)于直接為 FINAL_VALUE 賦值 “FINAL”,但是他就是可以!至于為什么,你這么想:null == null ? “FINAL” : null 是在運(yùn)行時(shí)刻計(jì)算的,在編譯時(shí)刻不會(huì)計(jì)算,也就不會(huì)被優(yōu)化,所以你懂得。
總結(jié)來(lái)說(shuō),不管使用構(gòu)造函數(shù)還是三目表達(dá)式,根本上都是避免在編譯時(shí)刻被優(yōu)化,這樣我們通過(guò)反射修改常量之后才有意義!好了,這一小部分到此結(jié)束!
最后的強(qiáng)調(diào):
必須提醒你的是,無(wú)論直接為常量賦值 、 通過(guò)構(gòu)造函數(shù)為常量賦值 還是 使用三目運(yùn)算符,實(shí)際上我們都能通過(guò)反射成功修改常量的值。而我在上面說(shuō)的修改”成功”與否是指:我們?cè)诔绦蜻\(yùn)行階段通過(guò)反射肯定能修改常量值,但是實(shí)際執(zhí)行優(yōu)化后的 .class 文件時(shí),修改的后值真的起到作用了嗎?換句話(huà)說(shuō),就是編譯時(shí)是否將常量替換為具體的值了?如果替換了,再怎么修改常量的值都不會(huì)影響最終的結(jié)果了,不是嗎?。
其實(shí),你可以直接這么想:反射肯定能修改常量的值,但修改后的值是否有意義?
到底能不能改?
到底能不能改?也就是說(shuō)反射修改后到底有沒(méi)有意義?
如果你上面看明白了,答案就簡(jiǎn)單了。俗話(huà)說(shuō)“一千句話(huà)不如一張圖”,下面允許我用不太規(guī)范的流程圖直接表達(dá)答案哈。
注:圖中”沒(méi)法修改”可以理解為”能修改值但沒(méi)有意義”;”可以修改”是指”能修改值且有意義”。
四、總結(jié)
好了,本次記錄就到這兒了,突然不知不覺(jué)發(fā)現(xiàn)寫(xiě)了好多,感謝耐心聽(tīng)我叨逼完。我想這篇博客如果你認(rèn)真的看完,肯定會(huì)有收獲的!最后,因?yàn)閮?nèi)容較多,知識(shí)點(diǎn)較多,如果文中有任何錯(cuò)誤或欠妥的地方,還望指正。
大家可以點(diǎn)擊加入群:478052716【JAVA高級(jí)程序員】里面有Java高級(jí)大牛直播講解知識(shí)點(diǎn) 走的就是高端路線(xiàn) (如果你想跳槽換工作 但是技術(shù)又不夠 或者工作上遇到了瓶頸 我這里有一個(gè)JAVA的免費(fèi)直播課程 講的是高端的知識(shí)點(diǎn)基礎(chǔ)不好的誤入喲 只要你有1-5年的開(kāi)發(fā)經(jīng)驗(yàn)可以加群找我要課堂鏈接 注意:是免費(fèi)的 沒(méi)有開(kāi)發(fā)經(jīng)驗(yàn)誤入哦)返回搜狐,查看更多
責(zé)任編輯:
總結(jié)
以上是生活随笔為你收集整理的java 反射 int_Java 反射由浅入深 | 进阶必备的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 蚂蚁集团 mPaaS 平台与华为达成合作
- 下一篇: 荣耀100 Pro正式发布 搭载单反级写