該文完全引自java中什么是bridge method
1秫逝、什么時候會生成橋接方法
就是說一個子類在繼承(或?qū)崿F(xiàn))一個父類(或接口)的泛型方法時渗勘,在子類中明確指定了泛型類型,那么在編譯時編譯器會自動生成橋接方法(當然還有其他情況會生成橋接方法游桩,這里只是列舉了其中一種情況)庄萎。如下所示:
package com.mikan;
/**
* @author Mikan
* @date 2015-08-05 16:22
*/
public interface SuperClass<T> {
T method(T param);
}
package com.mikan;
/**
* @author Mikan
* @date 2015-08-05 17:05
*/
public class SubClass implements SuperClass<String> {
public String method(String param) {
return param;
}
}
來看一下SubClass的字節(jié)碼:
localhost:mikan mikan$ javap -c SubClass.class
Compiled from "SubClass.java"
public class com.mikan.SubClass implements com.mikan.SuperClass<java.lang.String> {
public com.mikan.SubClass();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/mikan/SubClass;
public java.lang.String method(java.lang.String);
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: aload_1
1: areturn
LineNumberTable:
line 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 this Lcom/mikan/SubClass;
0 2 1 param Ljava/lang/String;
public java.lang.Object method(java.lang.Object);
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #2 // class java/lang/String
5: invokevirtual #3 // Method method:(Ljava/lang/String;)Ljava/lang/String;
8: areturn
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/mikan/SubClass;
0 9 1 x0 Ljava/lang/Object;
}
localhost:mikan mikan$
SubClass只聲明了一個方法种蝶,而從字節(jié)碼可以看到有三個方法扩借,第一個是無參的構(gòu)造方法(代碼中雖然沒有明確聲明襟铭,但是編譯器會自動生成),第二個是我們實現(xiàn)的接口中的方法翁都,第三個就是編譯器自動生成的橋接方法碍论。可以看到flags包括了ACC_BRIDGE和ACC_SYNTHETIC柄慰,表示是編譯器自動生成的方法鳍悠,參數(shù)類型和返回值類型都是Object。再看這個方法的字節(jié)碼坐搔,它把Object類型的參數(shù)強制轉(zhuǎn)換成了String類型藏研,再調(diào)用在SubClass類中聲明的方法,轉(zhuǎn)換過來其實就是:
public Object method(Object param) {
return this.method(((String) param));
}
也就是說概行,橋接方法實際是是調(diào)用了實際的泛型方法蠢挡,來看看下面的測試代碼:
package com.mikan;
/**
* @author Mikan
* @date 2015-08-07 16:33
*/
public class BridgeMethodTest {
public static void main(String[] args) throws Exception {
SuperClass superClass = new SubClass();
System.out.println(superClass.method("abc123"));// 調(diào)用的是實際的方法
System.out.println(superClass.method(new Object()));// 調(diào)用的是橋接方法
}
}
這里聲明了SuperClass類型的變量指向SubClass類型的實例,典型的多態(tài)。在聲明SuperClass類型的變量時袒哥,不指定泛型類型,那么在方法調(diào)用時就可以傳任何類型的參數(shù)消略,因為SuperClass中的方法參數(shù)實際上是Object類型(泛型擦除)堡称,而且編譯器也不能發(fā)現(xiàn)錯誤。在運行時當參數(shù)類型不是SubClass聲明的類型時艺演,會拋出類型轉(zhuǎn)換異常却紧,因為這時調(diào)用的是橋接方法,而在橋接方法中會進行強制類型轉(zhuǎn)換胎撤,所以才會拋出類型轉(zhuǎn)換異常晓殊。上面的代碼輸出結(jié)果如下:
abc123
Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
at com.mikan.SubClass.method(SubClass.java:7)
at com.mikan.BridgeMethodTest.main(BridgeMethodTest.java:27)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
如果我們在聲明SuperClass類型的變量就指定了泛型類型:
SuperClass<String> superClass = new SubClass();
當然這里類型只能是String,因為SubClass的泛型類型聲明是String類型的伤提,如果指定其他類型巫俺,那么在編譯時就會錯誤,這樣就把類型檢查從運行時提前到了編譯時肿男。這就是泛型的好處介汹。
2、為什么要生成橋接方法
上面看到了編譯器在什么時候會生成橋接方法舶沛,那為什么要生成橋接方法呢嘹承?
在java1.5以前,比如聲明一個集合類型:
List list = new ArrayList();
那么往list中可以添加任何類型的對象如庭,但是在從集合中獲取對象時叹卷,無法確定獲取到的對象是什么具體的類型,所以在1.5的時候引入了泛型坪它,在聲明集合的時候就指定集合中存放的是什么類型的對象:
List<String> list = new ArrayList<String>();
那么在獲取時就不必擔心類型的問題骤竹,因為泛型在編譯時編譯器會檢查往集合中添加的對象的類型是否匹配泛型類型,如果不正確會在編譯時就會發(fā)現(xiàn)錯誤往毡,而不必等到運行時才發(fā)現(xiàn)錯誤瘤载。因為泛型是在1.5引入的,為了向前兼容卖擅,所以會在編譯時去掉泛型(泛型擦除)鸣奔,但是我們還是可以通過反射API來獲取泛型的信息,在編譯時可以通過泛型來保證類型的正確性惩阶,而不必等到運行時才發(fā)現(xiàn)類型不正確挎狸。由于java泛型的擦除特性,如果不生成橋接方法断楷,那么與1.5之前的字節(jié)碼就不兼容了锨匆。如前面的SuperClass中的方法,實際在編譯后的字節(jié)碼如下:
localhost:mikan mikan$ javap -c -v SuperClass.class
Classfile /Users/mikan/Documents/workspace/project/algorithm/target/classes/com/mikan/SuperClass.class
Last modified 2015-8-7; size 251 bytes
MD5 checksum 2e2530041f1f83aaf416a2ca3af9b7e3
Compiled from "SuperClass.java"
public interface com.mikan.SuperClass<T extends java.lang.Object>
Signature: #7 // <T:Ljava/lang/Object;>Ljava/lang/Object;
SourceFile: "SuperClass.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
#1 = Class #10 // com/mikan/SuperClass
#2 = Class #11 // java/lang/Object
#3 = Utf8 method
#4 = Utf8 (Ljava/lang/Object;)Ljava/lang/Object;
#5 = Utf8 Signature
#6 = Utf8 (TT;)TT;
#7 = Utf8 <T:Ljava/lang/Object;>Ljava/lang/Object;
#8 = Utf8 SourceFile
#9 = Utf8 SuperClass.java
#10 = Utf8 com/mikan/SuperClass
#11 = Utf8 java/lang/Object
{
public abstract T method(T);
flags: ACC_PUBLIC, ACC_ABSTRACT
Signature: #6 // (TT;)TT;
}
localhost:mikan mikan$
通過Signature: #7 // <T:Ljava/lang/Object;>Ljava/lang/Object;可以看到,在編譯完成后泛型實際上就成了Object了恐锣,所以方法實際上成了
public abstract Object method(Object param);
而SubClass中的method方法是:
public String method(String param) {
return param;
}
SubClass實現(xiàn)了SuperClass這個接口茅主,如果不生成橋接方法,那么SubClass就沒有實現(xiàn)接口中聲明的方法土榴,語義就不正確了诀姚,所以編譯器才會自動生成橋接方法,來保證兼容性玷禽。
3赫段、如何通過橋接方法獲取實際的方法
我們在通過反射進行方法調(diào)用時,如果獲取到橋接方法對應的實際的方法呢矢赁?可以查看spring中org.springframework.core.BridgeMethodResolver類的源碼糯笙。實際上是通過判斷方法名、參數(shù)的個數(shù)以及泛型類型參數(shù)來獲取的撩银。