看到題目,不少初級開發同窗就慌了,我每天 crud,什麼時候用過反射呀?並且它竟然還有 bug 的嗎?html
其實否則,反射在咱們平常開發中一直陪伴着咱們,若是咱們一點不重視反射的使用,就會不自覺地寫出不少沒法理解的 bug。今天咱們就來看看增刪改查是如何遇到反射bug的!java
重載level方法,入參分別是int和Integer。 若不使用反射,選用哪一個重載方法很清晰,好比:git
Integer.valueOf(「666」)
走Integer重載那反射調用方法也是根據入參類型肯定使用哪一個重載方法嗎? 使用getDeclaredMethod
獲取 grade
方法,而後傳入Integer.valueOf(「36」)
結果是: 由於反射進行方法調用是經過bash
來肯定方法。本例的getDeclaredMethod
傳入的參數類型Integer.TYPE
其實表明int
。 因此無論傳包裝類型仍是基本類型,最終都是調用int入參重載方法。markdown
將Integer.TYPE
改成Integer.class
,則實際執行的參數類型就是Integer了。且不管傳包裝類型仍是基本類型,最終都調用Integer入參重載方法。oracle
綜上,反射調用方法,是以反射獲取方法時傳入的方法名和參數類型來肯定調用的方法。ide
泛型容許SE使用類型參數替代精確類型,實例化時再指明具體類型。利於代碼複用,將一套代碼應用到多種數據類型。oop
泛型的類型檢測,能夠在編譯時檢查不少泛型編碼錯誤。但因爲歷史兼容性而妥協的泛型類型擦除方案,在運行時還有不少坑。ui
如今指望在類的字段內容變更時記錄日誌,因而SE想到定義一個泛型父類,並在父類中定義一個統一的日誌記錄方法,子類可繼承該方法。上線後總有日誌重複記錄。編碼
雖Base.value正確設置爲了JavaEdge,但父類setValue調用了兩次,計數器顯示2
兩次調用Base.setValue,是由於getMethods找到了兩個setValue
:
setValue(T value)
泛型擦除後是setValue(Object value)
,因而子類入參String的setValue
被看成新方法setValue
未加@Override
註解,編譯器未能檢測到重寫失敗有的同窗會認爲是由於反射API使用錯誤致使而非重寫失敗:
getMethods
獲得當前類和父類的全部public
方法
getDeclaredMethods
得到當前類全部的public、protected、package和private方法
因而用getDeclaredMethods
替換getMethods
: 雖然這樣作能夠規避重複記錄日誌,但未解決子類重寫父類方法失敗的問題
setValue
因而,終於明白還得從新實現Sub2,繼承Base時將String做爲泛型T類型,並使用 @Override 註解 setValue
Sub2的setValue
居然調用了兩次,難道是JDK反射有Bug!getDeclaredMethods
查找到的方法確定來自Sub2
;並且Sub2看起來也就一個setValue,怎麼會重複?
調試發現,Child2類其實有倆setValue
:入參分別是String、Object。 這就是由於泛型類型擦除。
Java泛型類型在編譯後被擦除爲Object。子類雖指定父類泛型T類型是String,但編譯後T會被擦除成爲Object,因此父類setValue
入參是Object,value也是Object。 若Sub2.setValue想重寫父類,那入參也須爲Object。因此,編譯器會爲咱們生成一個橋接方法。 Sub2類的class字節碼:
➜ genericandinheritance git:(master) ✗ javap -c Sub2.class
Compiled from "GenericAndInheritanceApplication.java"
class com.javaedge.oop.genericandinheritance.Sub2 extends com.javaedge.oop.genericandinheritance.Base<java.lang.String> {
com.javaedge.oop.genericandinheritance.Sub2();
Code:
0: aload_0
1: invokespecial #1 // Method com/javaedge/oop/genericandinheritance/Base."<init>":()V
4: return
public void setValue(java.lang.String);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String call Sub2.setValue
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: aload_0
9: aload_1
10: invokespecial #5 // Method com/javaedge/oop/genericandinheritance/Base.setValue:(Ljava/lang/Object;)V
13: return
public void setValue(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: checkcast #6 // class java/lang/String
// 入參爲Object的setValue在內部調用了入參爲String的setValue方法
5: invokevirtual #7 // Method setValue:(Ljava/lang/String;)V
8: return
}
複製代碼
若編譯器未幫咱們實現該橋接方法,則Sub2重寫的是父類泛型類型擦除後、入參是Object的setValue。這兩個方法的參數,一個String一個Object,顯然不符Java重寫。
入參爲Object的橋接方法上標記了public synthetic bridge
:
知道了橋接方法的存在,如今就該知道如何修正代碼了。
參考