今天在Java中字節(jié)碼的格式的時候,發(fā)現(xiàn)method_info中的access_flags中竟然定了ACC_BRIDGE的值。網(wǎng)上搜了一下,大概理解它的意思了,先記之。
首先是在什么情況下會生成bridge方法(2):
bridge method may be created by the compiler when extending a parameterized type whose methods have parameterized arguments.
這是在網(wǎng)上找到的有人貼出來的一段話,但是感覺這段話說的并不是很明白。首先bridge方式是由編譯器產(chǎn)生的,因而在源代碼中也沒有bridge的關(guān)鍵字。然后只有在以具體類型繼承自一個泛型類,同時被繼承的泛型類包含了泛型方法。比如看以下的例子:
abstract class A<T> {
public abstract T method1(T arg);
public abstract T method2();
}
class B extends A<String> {
public String method1(String arg) {
return arg;
}
public String method2() {
return "abc";
}
}
class C<T> extends A<T> {
public T method1(T arg) {
return arg;
}
public T method2() {
return null;
}
}
他們生成的.class文件如下:
A.class
abstract class org.levin.insidejvm.miscs.bridgemethod.A {
public abstract java.lang.Object method1(java.lang.Object arg0);
public abstract java.lang.Object method2();
}
B.class
class org.levin.insidejvm.miscs.bridgemethod.B extends org.levin.insidejvm.miscs.bridgemethod.A {
public java.lang.String method1(java.lang.String arg);
0 aload_1 [arg]
1 areturn
public java.lang.String method2();
0 ldc <String "abc"> [20]
2 areturn
public bridge synthetic java.lang.Object method2();
0 aload_0 [this]
1 invokevirtual org.levin.insidejvm.miscs.bridgemethod.B.method2() : java.lang.String [23]
4 areturn
public bridge synthetic java.lang.Object method1(java.lang.Object arg0);
0 aload_0 [this]
1 aload_1 [arg0]
2 checkcast java.lang.String [26]
5 invokevirtual org.levin.insidejvm.miscs.bridgemethod.B.method1(java.lang.String) : java.lang.String [28]
8 areturn
}
C.class
class org.levin.insidejvm.miscs.bridgemethod.C extends org.levin.insidejvm.miscs.bridgemethod.A {
public java.lang.Object method1(java.lang.Object arg);
0 aload_1 [arg]
1 areturn
public java.lang.Object method2();
0 aconst_null
1 areturn
}
可以看到B中生成了兩個bridge方法,而C中則沒有。事實(shí)上,由于Java中泛型有擦除的機(jī)制,因而在編譯A類的時候,它里面定義的方法都是以Object類型來表示了,因而如果沒有bridge方法,B類根本沒有覆蓋A類中的abstract方法。正因?yàn)橛?/span>bridge方法的存在,才使得B類可以編譯通過。而C類由于在編譯時所有的泛型也都是通過Object類來表達(dá)的,因而它實(shí)現(xiàn)的也是A類中的abstract方法,因而不用再生成bridge方法了。
事實(shí)上B類中的bridge方法在調(diào)用也有一些區(qū)別:
public static void main(String[] args) {
B b = new B();
b.method1("abc");
A<String> a = new B();
a.method1("abc");
}
這段方法的字節(jié)碼如下:
0 new org.levin.insidejvm.miscs.bridgemethod.B [16]
3 dup
4 invokespecial org.levin.insidejvm.miscs.bridgemethod.B() [18]
7 astore_1 [b]
8 aload_1 [b]
9 ldc <String "abc"> [19]
11 invokevirtual org.levin.insidejvm.miscs.bridgemethod.B.method1(java.lang.String) : java.lang.String [21]
14 pop
15 new org.levin.insidejvm.miscs.bridgemethod.B [16]
18 dup
19 invokespecial org.levin.insidejvm.miscs.bridgemethod.B() [18]
22 astore_2 [a]
23 aload_2 [a]
24 ldc <String "abc"> [19]
26 invokevirtual org.levin.insidejvm.miscs.bridgemethod.A.method1(java.lang.Object) : java.lang.Object [25]
29 pop
30 return
以上的代碼可以看出b變量調(diào)用的method1(String)的方法,而a變量調(diào)用的卻是method1(Object)方法。這種區(qū)別也正式因?yàn)?/span>bridge方法提供的支持才實(shí)現(xiàn)的。
事實(shí)上,bridge方法還會在另外一種情況下產(chǎn)生(2):
在Java 1.4中,子類若要重寫父類某個方法,那么子類的方法和父類的方法簽名必須完全一致,包括方法名、參數(shù)類型以及返回值;而到Java 1.5中,該機(jī)制變成,如果子類中某個方法的方法名和參數(shù)類型和父類某方法一致,并且子類該方法的返回值是父類相應(yīng)方法返回值的類型或其子類型,那么該子類方法也可以重寫父類中相應(yīng)的方法。參看以下例子:
class E {
}
class F extends E {
}
class X {
public E getE() {
return new E();
}
}
class Y extends X {
@Override
public F getE() {
return new F();
}
}
以上代碼是可以編譯通過的。讓我們再來查看一下Y的字節(jié)碼:
class org.levin.insidejvm.miscs.bridgemethod.Y extends org.levin.insidejvm.miscs.bridgemethod.X {
public org.levin.insidejvm.miscs.bridgemethod.F getE();
0 new org.levin.insidejvm.miscs.bridgemethod.F [16]
3 dup
4 invokespecial org.levin.insidejvm.miscs.bridgemethod.F() [18]
7 areturn
public bridge synthetic org.levin.insidejvm.miscs.bridgemethod.E getE();
0 aload_0 [this]
1 invokevirtual org.levin.insidejvm.miscs.bridgemethod.Y.getE() : org.levin.insidejvm.miscs.bridgemethod.F [20]
4 areturn
}
從字節(jié)碼上,我們可以看出語法本身事實(shí)上并沒有發(fā)生變化,變化的只是編譯器做的支持,它為重載方法重新生成了一個返回E而不是F的bridge方法。
從調(diào)用的字節(jié)碼上可以更加明顯的看出語法沒有發(fā)生變化這一點(diǎn):
public static void main(String[] args) {
X x = new Y();
x.getE();
}
字節(jié)碼如下:
public static void main(java.lang.String[] args);
0 new org.levin.insidejvm.miscs.bridgemethod.Y [16]
3 dup
4 invokespecial org.levin.insidejvm.miscs.bridgemethod.Y() [18]
7 astore_1 [x]
8 aload_1 [x]
9 invokevirtual org.levin.insidejvm.miscs.bridgemethod.X.getE() : org.levin.insidejvm.miscs.bridgemethod.E [19]
12 pop
13 return
該字節(jié)碼中x.getE()方法事實(shí)上調(diào)用的就是生成的bridge方法(E getE())方法,而不是用戶定義的F getE()方法。
這種重載機(jī)制在某些,不同子類某個函數(shù)的返回值是不一樣的,但是他們都需要重寫父類中方法,以可以在某個點(diǎn)上通過父類實(shí)例統(tǒng)一調(diào)用。只是這種機(jī)制就需要返回值必須是繼承于同一個類。事實(shí)上,這種方式在沒有引入這種重寫機(jī)制的時候也是可以實(shí)現(xiàn)的,只是現(xiàn)在Java在編譯器層面上提供了支持。
于2010年10月3日
注:這些文章都是前些時候?qū)懙?,之前博客很亂,也都是隨便貼一些自己寫的或轉(zhuǎn)載的,還有一些則是沒有貼出來過的?,F(xiàn)在打算好好整理一下,完整的記錄自己的一些學(xué)習(xí)歷程,而每次看到過去的時間,則讓我想起以前的日子,因而我對時間一直是很重視的,所以每篇都著名寫的日期,直到最先的文章出現(xiàn)。:)