- 原文地址:Java bridge methods explained
- 原文作者:STAS
- 譯文出自:掘金翻譯計(jì)劃
- 本文永久鏈接:https://github.com/xitu/gold-miner/blob/master/TODO1/java-bridge-methods-explained.md
- 譯者:kezhenxu94
- 校對(duì)者:Starrier
Java 橋接方法詳解
Java 中的橋接方法是一種合成方法,在實(shí)現(xiàn)某些 Java 語(yǔ)言特性的時(shí)候是很有必要的。最為人熟知的例子就是協(xié)變返回值類(lèi)型和泛型擦除后導(dǎo)致基類(lèi)方法的參數(shù)與實(shí)際調(diào)用的方法參數(shù)類(lèi)型不一致。
看一下以下的例子:
public class SampleOne {
public static class A<T> {
public T getT() {
return null;
}
}
public static class B extends A<String> {
public String getT() {
return null;
}
}
}
事實(shí)上這就是一個(gè)協(xié)變返回類(lèi)型的例子,泛型擦除后將會(huì)變成類(lèi)似于下面這樣的代碼段:
public class SampleOne {
public static class A {
public Object getT() {
return null;
}
}
public static class B extends A {
public String getT() {
return null;
}
}
}
在將編譯后的字節(jié)碼反編譯后,類(lèi) B 會(huì)是這樣子的:
public class SampleOne$B extends SampleOne$A {
public SampleOne$B();
...
public java.lang.String getT();
Code:
0: aconst_null
1: areturn
public java.lang.Object getT();
Code:
0: aload_0
1: invokevirtual #2; // 調(diào)用 getT:()Ljava/lang/String;
4: areturn
}
從上面可以看到,有一個(gè)新合成的方法 java.lang.Object getT(), 這在源代碼中是沒(méi)有出現(xiàn)過(guò)的。這個(gè)方法就起了一個(gè)橋接的作用,它所做的就是把對(duì)自身的調(diào)用委托給方法 jva.lang.String getT()。編譯器不得不這么做,因?yàn)樵?JVM 方法中,返回類(lèi)型也是方法簽名的一部分,而橋接方法的創(chuàng)建就正好是實(shí)現(xiàn)協(xié)變返回值類(lèi)型的方式。
現(xiàn)在再看一看下面和泛型相關(guān)的例子:
public class SampleTwo {
public static class A<T> {
public T getT(T args) {
return args;
}
}
public static class B extends A<String> {
public String getT(String args) {
return args;
}
}
}
編譯后類(lèi) B 會(huì)變成下面這樣子:
public class SampleThree$B extends SampleThree$A{
public SampleThree$B();
...
public java.lang.String getT(java.lang.String);
Code:
0: aload_1
1: areturn
public java.lang.Object getT(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: checkcast #2; //class java/lang/String
5: invokevirtual #3; //Method getT:(Ljava/lang/String;)Ljava/lang/String;
8: areturn
}
這里的橋接方法覆蓋了(override)基類(lèi) A 的方法,不僅使用字符串參數(shù)將對(duì)自身的調(diào)用委派給基類(lèi) A 的方法,同時(shí)也執(zhí)行了一個(gè)到 java.lang.String 的類(lèi)型轉(zhuǎn)換檢測(cè)(#2)。這就意味著如果你運(yùn)行下面這樣的代碼,忽略編譯器的“未檢”(unchecked)警告,結(jié)果會(huì)是從橋接方法那里拋出異常 ClassCastException。
A a = new B();
a.getT(new Object()));
以上例子就是橋接方法最為人熟知的兩種使用場(chǎng)景,但至少還有一種使用案例,就是橋接方法被用于“改變”基類(lèi)可見(jiàn)性。考慮以下示例代碼,猜測(cè)一下編譯器是否需要?jiǎng)?chuàng)建一個(gè)橋接方法:
package samplefour;
public class SampleFour {
static class A {
public void foo() {
}
}
public static class C extends A {
}
public static class D extends A {
public void foo() {
}
}
}
如果你反編譯 C 類(lèi),你將會(huì)看到有 foo 方法,它覆蓋了基類(lèi)的方法并把對(duì)自身的調(diào)用委托給它(基類(lèi)的方法):
public class SampleFour$C extends SampleFour$A{
...
public void foo();
Code:
0: aload_0
1: invokespecial #2; //Method SampleFour$A.foo:()V
4: return
}
編譯器需要這樣的方法,因?yàn)?A 類(lèi)不是公開(kāi)的,在 A 類(lèi)所在包之外是不可見(jiàn)的,但是 C 類(lèi)是公開(kāi)的,它所繼承來(lái)的所有方法在所在包之外也都應(yīng)該是可見(jiàn)的。需要注意的是,D 類(lèi)不會(huì)有橋接方法生成,因?yàn)樗采w了 foo 方法,因此沒(méi)有必要“提升”其可見(jiàn)性。
這種橋接方法似乎是由于這個(gè) bug(在 Java 6 被修復(fù))才引入的。這意味著在 Java 6 之前是不會(huì)生成這樣橋接方法的,那么 C#foo 就不能夠在它所在包之外使用反射調(diào)用,以致于下面這樣的代碼在 Java 版本小于 1.6 時(shí)會(huì)報(bào) IllegalAccessException 異常。
package samplefive;
...
SampleFour.C.class.getMethod("foo").invoke(new SampleFour.C());
...
不使用反射機(jī)制,正常調(diào)用的話是起作用的。
可能還有其他使用橋接方法的案例,但沒(méi)有相關(guān)的資料。此外,關(guān)于橋接方法也沒(méi)有明確的定義,盡管你可以很容易的猜測(cè)出來(lái),像以上的示例是相當(dāng)明顯的,但如果有一些規(guī)范把橋接方法說(shuō)明清楚的話就更好了。盡管自 Java 5 開(kāi)始 Method#isBridge() 方法 就是公開(kāi)的反射 API 了,橋接的標(biāo)志也是字節(jié)碼文件格式中的一部分,但 Java 虛擬機(jī)和 Java 語(yǔ)言規(guī)范都始終沒(méi)有任何關(guān)于橋接方法的確切文檔,也沒(méi)有提供關(guān)于編譯器何時(shí)/如何使用橋接方法的任何規(guī)則。我所能找到的全部引用都是來(lái)自這里的“討論區(qū)”。
掘金翻譯計(jì)劃 是一個(gè)翻譯優(yōu)質(zhì)互聯(lián)網(wǎng)技術(shù)文章的社區(qū),文章來(lái)源為 掘金 上的英文分享文章。內(nèi)容覆蓋 Android、iOS、前端、后端、區(qū)塊鏈、產(chǎn)品、設(shè)計(jì)、人工智能等領(lǐng)域,想要查看更多優(yōu)質(zhì)譯文請(qǐng)持續(xù)關(guān)注 掘金翻譯計(jì)劃、官方微博、知乎專(zhuān)欄。