一犯祠、Java 反射機制
參考了許多博文,總結了以下個人觀點酌呆,若有不妥還望指正:
Java 反射機制在程序運行時衡载,對于任意一個類,都能夠知道這個類的所有屬性和方法隙袁;對于任意一個對象痰娱,都能夠調用它的任意一個方法和屬性。這種 動態(tài)的獲取信息 以及動態(tài)調用對象的方法 的功能稱為 java 的反射機制菩收。
反射機制很重要的一點就是“運行時”梨睁,其使得我們可以在程序運行時加載、探索以及使用編譯期間完全未知的 .class 文件娜饵。換句話說坡贺,Java 程序可以加載一個運行時才得知名稱的 .class 文件,然后獲悉其完整構造,并生成其對象實體遍坟、或對其 fields(變量)設值拳亿、或調用其 methods(方法)。
不知道上面的理論你能否明白政鼠,反正剛接觸反射時我一臉懵比风瘦,后來寫了幾個例子之后:哦~~原來是這個意思!
若暫時不明白理論沒關系公般,先往下看例子万搔,之后再回來看相信你就能明白了。
二官帘、使用反射獲取類的信息
為使得測試結果更加明顯瞬雹,我首先定義了一個 FatherClass 類(默認繼承自 Object 類),然后定義一個繼承自 FatherClass 類的 SonClass 類刽虹,如下所示酗捌。可以看到測試類中變量以及方法的訪問權限不是很規(guī)范涌哲,是為了更明顯得查看測試結果而故意設置的胖缤,實際項目中不提倡這么寫。
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. 獲取類的所有變量信息
/**
* 通過反射獲取類的所有變量
*/
private static void printFields(){
//1.獲取并輸出類的名稱
Class mClass = SonClass.class;
System.out.println(“類的名稱:” + mClass.getName());
//2.1 獲取所有 public 訪問權限的變量// 包括本類聲明的和從父類繼承的Field[] fields = mClass.getFields();//2.2 獲取所有本類聲明的變量(不問訪問權限)//Field[] fields = mClass.getDeclaredFields();//3. 遍歷變量并輸出變量信息for (Field field : fields) { //獲取訪問權限并輸出 int modifiers = field.getModifiers(); System.out.print(Modifier.toString(modifiers) + " "); //輸出變量的類型及變量名 System.out.println(field.getType().getName() + " " + field.getName());}
}
以上代碼注釋很詳細阀圾,就不再解釋了哪廓。需要注意的是注釋中 2.1 的 getFields() 與 2.2的getDeclaredFields() 之間的區(qū)別,下面分別看一下兩種情況下的輸出初烘∥姓妫看之前強調一下:
SonClass extends FatherClass extends Object :
調用 getFields() 方法,輸出 SonClass 類以及其所繼承的父類( 包括 FatherClass 和Object ) 的 public 方法肾筐。注:Object 類中沒有成員變量哆料,所以沒有輸出。
類的名稱:obj.SonClass
public java.lang.String mSonBirthday
public java.lang.String mFatherName
public int mFatherAge
調用 getDeclaredFields() 吗铐, 輸出 SonClass 類的所有成員變量东亦,不問訪問權限。
類的名稱:obj.SonClass
private java.lang.String mSonName
protected int mSonAge
public java.lang.String mSonBirthday
2. 獲取類的所有方法信息
/**
* 通過反射獲取類的所有方法
*/
private static void printMethods(){
//1.獲取并輸出類的名稱
Class mClass = SonClass.class;
System.out.println(“類的名稱:” + mClass.getName());
//2.1 獲取所有 public 訪問權限的方法//包括自己聲明和從父類繼承的Method[] mMethods = mClass.getMethods();//2.2 獲取所有本類的的方法(不問訪問權限)//Method[] mMethods = mClass.getDeclaredMethods();//3.遍歷所有方法for (Method method : mMethods) { //獲取并輸出方法的訪問權限(Modifiers:修飾符) int modifiers = method.getModifiers(); System.out.print(Modifier.toString(modifiers) + " "); //獲取并輸出方法的返回值類型 Class returnType = method.getReturnType(); System.out.print(returnType.getName() + " " + method.getName() + "( "); //獲取并輸出方法的所有參數 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ū)別典阵,下面看一下打印輸出:
調用 getMethods() 方法
獲取 SonClass 類所有 public 訪問權限的方法,包括從父類繼承的谣妻。打印信息中,printSonMsg() 方法來自 SonClass 類卒稳, printFatherMsg() 來自 FatherClass 類蹋半,其余方法來自 Object 類。
類的名稱: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( )
調用 getDeclaredMethods() 方法
打印信息中充坑,輸出的都是 SonClass 類的方法减江,不問訪問權限染突。
類的名稱: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( )
三、訪問或操作類的私有變量和方法
在上面辈灼,我們成功獲取了類的變量和方法信息份企,驗證了在運行時 動態(tài)的獲取信息 的觀點。那么巡莹,僅僅是獲取信息嗎司志?我們接著往后看。
都知道降宅,對象是無法訪問或操作類的私有變量和方法的骂远,但是,通過反射腰根,我們就可以做到激才。沒錯,反射可以做到额嘿!下面瘸恼,讓我們一起探討如何利用反射訪問 類對象的私有方法 以及修改 私有變量或常量。
老規(guī)矩册养,先上測試類东帅。
注:
請注意看測試類中變量和方法的修飾符(訪問權限);
測試類僅供測試捕儒,不提倡實際開發(fā)時這么寫 : )
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 訪問私有方法
以訪問 TestClass 類中的私有方法 privateMethod(…) 為例冰啃,方法加參數是為了考慮最全的情況,很貼心有木有刘莹?先貼代碼阎毅,看注釋,最后我會重點解釋部分代碼点弯。
/**
* 訪問對象的私有方法
* 為簡潔代碼扇调,在方法上拋出總的異常,實際開發(fā)別這樣
*/
private static void getPrivateMethod() throws Exception{
//1. 獲取 Class 類實例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();
//2. 獲取私有方法//第一個參數為要獲取的私有方法的名稱//第二個為要獲取方法的參數的類型抢肛,參數為 Class...狼钮,沒有參數就是null//方法參數也可這么寫 :new Class[]{String.class , int.class}Method privateMethod = mClass.getDeclaredMethod("privateMethod", String.class, int.class);//3. 開始操作方法if (privateMethod != null) { //獲取私有方法的訪問權 //只是獲取訪問權,并不是修改實際權限 privateMethod.setAccessible(true); //使用 invoke 反射調用私有方法 //privateMethod 是獲取到的私有方法 //testClass 要操作的對象 //后面兩個參數傳實參 privateMethod.invoke(testClass, "Java Reflect ", 666);}
}
需要注意的是捡絮,第3步中的 setAccessible(true) 方法熬芜,是獲取私有方法的訪問權限,如果不加會報異常 IllegalAccessException福稳,因為當前方法訪問權限是“private”的涎拉,如下:
java.lang.IllegalAccessException: Class MainClass can not access a member of class obj.TestClass with modifiers “private”
正常運行后,打印如下,調用私有方法成功:
Java Reflect 666
3.2 修改私有變量
以修改 TestClass 類中的私有變量 MSG 為例鼓拧,其初始值為 “Original” 半火,我們要修改為 “Modified”。老規(guī)矩季俩,先上代碼看注釋钮糖。
/**
* 修改對象私有變量的值
* 為簡潔代碼,在方法上拋出總的異常
*/
private static void modifyPrivateFiled() throws Exception {
//1. 獲取 Class 類實例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();
//2. 獲取私有變量Field privateField = mClass.getDeclaredField("MSG");//3. 操作私有變量if (privateField != null) { //獲取私有變量的訪問權 privateField.setAccessible(true); //修改私有變量酌住,并輸出以測試 System.out.println("Before Modify:MSG = " + testClass.getMsg()); //調用 set(object , value) 修改變量的值 //privateField 是獲取到的私有變量 //testClass 要操作的對象 //"Modified" 為要修改成的值 privateField.set(testClass, "Modified"); System.out.println("After Modify:MSG = " + testClass.getMsg());}
}
此處代碼和訪問私有方法的邏輯差不多店归,就不再贅述,從輸出信息看出 修改私有變量 成功:
Before Modify:MSG = Original
After Modify:MSG = Modified
3.3 修改私有常量
在 3.2 中赂韵,我們介紹了如何修改私有 變量娱节,現在來說說如何修改私有 常量,
真的能修改嗎祭示?
常量是指使用 final 修飾符修飾的成員屬性肄满,與變量的區(qū)別就在于有無 final 關鍵字修飾。在說之前质涛,先補充一個知識點稠歉。
Java 虛擬機(JVM)在編譯 .java 文件得到 .class 文件時,會優(yōu)化我們的代碼以提升效率汇陆。其中一個優(yōu)化就是:JVM 在編譯階段會把引用常量的代碼替換成具體的常量值怒炸,如下所示(部分代碼)。
編譯前的 .java 文件:
//注意是 String 類型的值
private final String FINAL_VALUE = “hello”;
if(FINAL_VALUE.equals(“world”)){
//do something
}
編譯后得到的 .class 文件(當然毡代,編譯后是沒有注釋的):
private final String FINAL_VALUE = “hello”;
//替換為”hello”
if(“hello”.equals(“world”)){
//do something
}
但是阅羹,并不是所有常量都會優(yōu)化。經測試對于 int 教寂、long 捏鱼、boolean 以及 String 這些基本類型 JVM 會優(yōu)化,而對于 Integer 酪耕、Long 导梆、Boolean 這種包裝類型,或者其他諸如Date 迂烁、Object 類型則不會被優(yōu)化看尼。
總結來說:對于基本類型的靜態(tài)常量,JVM 在編譯階段會把引用此常量的代碼替換成具體的常量值盟步。
這么說來藏斩,在實際開發(fā)中,如果我們想修改某個類的常量值却盘,恰好那個常量是基本類型的狰域,豈不是無能為力了窜觉?反正我個人認為除非修改源碼,否則真沒辦法北专!
這里所謂的無能為力是指:我們在程序運行時刻依然可以使用反射修改常量的值(后面會代碼驗證),但是 JVM 在編譯階段得到的 .class 文件已經將常量優(yōu)化為具體的值旬陡,在運行階段就直接使用具體的值了拓颓,所以即使修改了常量的值也已經毫無意義了。
下面我們驗證這一點描孟,在測試類 TestClass 類中添加如下代碼:
//String 會被 JVM 優(yōu)化
private final String FINAL_VALUE = “FINAL”;
public String getFinalValue(){
//劇透驶睦,會被優(yōu)化為: return “FINAL” ,拭目以待吧
return FINAL_VALUE;
}
接下來,是修改常量的值匿醒,先上代碼场航,請仔細看注釋:
/**
* 修改對象私有常量的值
* 為簡潔代碼,在方法上拋出總的異常廉羔,實際開發(fā)別這樣
*/
private static void modifyFinalFiled() throws Exception {
//1. 獲取 Class 類實例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();
//2. 獲取私有常量Field finalField = mClass.getDeclaredField("FINAL_VALUE");//3. 修改常量的值if (finalField != null) { //獲取私有常量的訪問權 finalField.setAccessible(true); //調用 finalField 的 getter 方法 //輸出 FINAL_VALUE 修改前的值 System.out.println("Before Modify:FINAL_VALUE = " + finalField.get(testClass)); //修改私有常量 finalField.set(testClass, "Modified"); //調用 finalField 的 getter 方法 //輸出 FINAL_VALUE 修改后的值 System.out.println("After Modify:FINAL_VALUE = " + finalField.get(testClass)); //使用對象調用類的 getter 方法 //獲取值并輸出 System.out.println("Actually :FINAL_VALUE = " + testClass.getFinalValue());}
}
上面的代碼不解釋了溉痢,注釋巨詳細有木有!特別注意一下第3步的注釋憋他,然后來看看輸出孩饼,已經迫不及待了,擦亮雙眼:
Before Modify:FINAL_VALUE = FINAL
After Modify:FINAL_VALUE = Modified
Actually :FINAL_VALUE = FINAL
結果出來了:
第一句打印修改前 FINAL_VALUE 的值竹挡,沒有異議镀娶;
第二句打印修改后常量的值,說明FINAL_VALUE確實通過反射修改了揪罕;
第三句打印通過 getFinalValue() 方法獲取的 FINAL_VALUE 的值梯码,但還是初始值,導致修改無效好啰!
這結果你覺得可信嗎轩娶?什么,你還不信坎怪?問我怎么知道 JVM 編譯后會優(yōu)化代碼罢坝?那要不這樣吧,一起來看看 TestClass.java 文件編譯后得到的 TestClass.class 文件搅窿。為避免說代碼是我自己手寫的嘁酿,我決定不粘貼代碼,直接截圖:
TestClass.class 文件
TestClass.class 文件
看到了吧男应,有圖有真相闹司,getFinalValue() 方法直接 return “FINAL”!同時也說明了沐飘,程序運行時是根據編譯后的 .class 來執(zhí)行的游桩。
順便提一下牲迫,如果你有時間,可以換幾個數據類型試試借卧,正如上面說的盹憎,有些數據類型是不會優(yōu)化的。你可以修改數據類型后铐刘,根據我的思路試試陪每,看輸出覺得不靠譜就直接看.classs 文件,一眼就能看出來哪些數據類型優(yōu)化了 镰吵,哪些沒有優(yōu)化檩禾。下面說下一個知識點。
想辦法也要修改疤祭!
不能修改盼产,這你能忍?別著急勺馆,不知你發(fā)現沒戏售,剛才的常量都是在聲明時就直接賦值了。你可能會疑惑草穆,常量不都是在聲明時賦值嗎蜈项?不賦值不報錯?當然不是啦续挟。
方法一
事實上紧卒,Java 允許我們聲明常量時不賦值,但必須在構造函數中賦值诗祸。你可能會問我為什么要說這個跑芳,這就解釋:
我們修改一下 TestClass 類,在聲明常量時不賦值直颅,然后添加構造函數并為其賦值博个,大概看一下修改后的代碼(部分代碼 ):
public class TestClass {
//......private final String FINAL_VALUE;//構造函數內為常量賦值public TestClass(){ this.FINAL_VALUE = "FINAL";}//......
}
現在,我們再調用上面貼出的修改常量的方法功偿,發(fā)現輸出是這樣的:
Before Modify:FINAL_VALUE = FINAL
After Modify:FINAL_VALUE = Modified
Actually :FINAL_VALUE = Modified
納尼盆佣,最后一句輸出修改后的值了?對械荷,修改成功了共耍!想知道為啥,還得看編譯后的TestClass.class 文件的貼圖吨瞎,圖中有標注痹兜。
TestClass.class 文件
TestClass.class 文件
解釋一下:我們將賦值放在構造函數中,構造函數是我們運行時 new 對象才會調用的颤诀,所以就不會像之前直接為常量賦值那樣字旭,在編譯階段將 getFinalValue() 方法優(yōu)化為返回常量值对湃,而是指向 FINAL_VALUE ,這樣我們在運行階段通過反射修改敞亮的值就有意義啦遗淳。但是拍柒,看得出來,程序還是有優(yōu)化的屈暗,將構造函數中的賦值語句優(yōu)化了斤儿。再想想那句 程序運行時是根據編譯后的 .class 來執(zhí)行的 ,相信你一定明白為什么這么輸出了恐锦!
方法二
請你務必將上面捋清楚了再往下看。接下來再說一種改法疆液,不使用構造函數一铅,也可以成功修改常量的值,但原理上都一樣堕油。去掉構造函數潘飘,將聲明常量的語句改為使用三目表達式賦值:
private final String FINAL_VALUE
= null == null ? “FINAL” : null;
其實,上述代碼等價于直接為 FINAL_VALUE 賦值 “FINAL”掉缺,但是他就是可以卜录!至于為什么,你這么想:null == null ? “FINAL” : null 是在運行時刻計算的眶明,在編譯時刻不會計算艰毒,也就不會被優(yōu)化,所以你懂得搜囱。
總結來說丑瞧,不管使用構造函數還是三目表達式,根本上都是避免在編譯時刻被優(yōu)化蜀肘,這樣我們通過反射修改常量之后才有意義绊汹!好了,這一小部分到此結束扮宠!
最后的強調:
必須提醒你的是西乖,無論直接為常量賦值 、 通過構造函數為常量賦值 還是 使用三目運算符坛增,實際上我們都能通過反射成功修改常量的值获雕。而我在上面說的修改”成功”與否是指:我們在程序運行階段通過反射肯定能修改常量值,但是實際執(zhí)行優(yōu)化后的 .class 文件時收捣,修改的后值真的起到作用了嗎典鸡?換句話說,就是編譯時是否將常量替換為具體的值了坏晦?如果替換了萝玷,再怎么修改常量的值都不會影響最終的結果了嫁乘,不是嗎?球碉。
其實蜓斧,你可以直接這么想:反射肯定能修改常量的值,但修改后的值是否有意義睁冬?
到底能不能改挎春?
到底能不能改?也就是說反射修改后到底有沒有意義豆拨?
如果你上面看明白了直奋,答案就簡單了。俗話說“一千句話不如一張圖”施禾,下面允許我用不太規(guī)范的流程圖直接表達答案哈脚线。
注:圖中”沒法修改”可以理解為”能修改值但沒有意義”;”可以修改”是指”能修改值且有意義”弥搞。
判斷能不能改
大家可以點擊加入群:478052716【JAVA高級程序員】 里面有Java高級大牛直播講解知識點 走的就是高端路線 (如果你想跳槽換工作 但是技術又不夠 或者工作上遇到了 瓶頸 我這里有一個JAVA的免費直播課程 講的是高端的知識點 基礎不好的勿入喲 只要你有1-5年的開發(fā)經驗 可以加群找我要課堂鏈接 注意:是免費的 沒有開發(fā)經驗勿入哦)