1.什么是橋接方法
橋接方法是 JDK 1.5 引入泛型后,為了使Java的泛型方法生成的字節碼和 1.5 版本前的字節碼相兼容,由編譯器自動生成的方法。
判斷方法
我們可以通過 Method.isBridge()
來判斷一個方法是不是橋接方法。
橋接方法的 access_flag
在字節碼中,橋接方法會被標記 ACC_BRIDGE 和 ACC_SYNTHETIC
ACC_BRIDGE
用來說明 橋接方法是由 Java 編譯器 生成的ACC_SYNCTHETIC
用來表示 該類成員沒有出現在源代碼中,而是由編譯器生成
2.什么情況下會產生橋接方法(探究)
情況1:實現一個泛型接口
Consumer<T>
是 JDK 1.8 出現的接口方法:
public interface Consumer<T> {
void accept(T t);
}
我們來實現該接口
public class StringConsumer implements Consumer<String> {
@Override
public void accept(String s) {
System.out.println("i consumed " + s);
}
}
編譯源代碼后,查看字節碼。
- 使用 IDEA: View -> Show Bytecode
我們發現:
StringConsumer 類的字節碼中包含 橋接方法
StringConsumer 類的字節碼中還包含我們覆寫后的 public void accept(String s)
方法
類似地,還有 JDK 1.8 出現的 Supplier<T>
和 Function<T,R>
, 接口實現類
public class StringSupplier implements Supplier<String> {
@Override
public String get() {
return "null";
}
}
橋接方法如圖所示:
相當於字節碼中有一個源代碼中所不存在的,由編譯器產生的public Object get()
方法。
public class StringFunction implements Function<String, String> {
@Override
public String apply(String s) {
return s + " , hello";
}
}
橋接方法如圖所示:
相當於字節碼中有一個源代碼中所不存在的,由編譯器產生的public Object apply(Object p)
方法。
情況2:覆蓋超類的方法,並升級返回類型
- 修改參數類型(×)
修改參數數量,類型,參數順序,都會產生一個新的方法。
// 超類
public class BaseConsumer {
public void accept(Object object) {}
}
// 子類
public class IntegerConsumer extends BaseConsumer {
public void accept(Object integer) {
System.out.println("consumed " + integer);
}
}
- 修改返回類型(√)
查看IntegerSupplier
字節碼可以找到橋接方法public Object get()
// 超類
public class BaseSupplier {
public Object get() {
return "base";
}
}
// 子類
public class IntegerSupplier extends BaseSupplier {
public Integer get() {
return 110;
}
}
- 保持原樣(×)
我們知道修改了參數不會產生橋接方法,但是我們保持方法的返回類型,方法名,參數,它也還是不會產生橋接方法
// 超類
public class BaseFunction {
public Object apply(Object src) {
return src;
}
}
// 子類
public class IntegerFunction extends BaseFunction {
public Object apply(Object src) {
return src;
}
}
情況3:升級修飾符不會生成橋接方法
- 訪問限定符升級(×)
// 超類
public class BasePrinter {
protected void print() {
}
}
// 子類
public class PublicPrinter extends BasePrinter {
public void print() {
System.out.println("print");
}
}
情況4:靜態方法不會生成橋接方法
- 子類的靜態方法屬於子類,父類的靜態方法屬於父類。
- 靜態方法在繼承中不存在覆蓋。也不會生成橋接方法
// 超類
public class SuperClass {
public static void staticMethod() {
System.out.println("SuperClass");
}
}
// 子類
public class SubClass extends SuperClass {
public static void staticMethod() {
System.out.println("SubClass");
}
}
3. 為什么要生成橋接方法
我們再來看這個方法
public class StringFunction implements Function<String, String> {
@Override
public String apply(String s) {
return s + " , hello";
}
public static void main(String[] args) {
Function func = new StringFunction();
System.out.println(func.apply(new Object()));
}
}
運行main函數,會產生以下異常。
發生錯誤的代碼是func.apply(new Object())
,這里會調用橋接方法。
查看字節碼,我們發現,橋接方法進行運行時類型檢查時,會拋出異常,但是真正發生作用的還是 StringFunction 類 String apply(String s)
。
我們重新聚焦為什么要生成橋接方法
在java1.5以前,比如聲明一個集合類型:
List list = new ArrayList();
那么往list中可以添加任何類型的對象,但是在從集合中獲取對象時,無法確定獲取到的對象是什么具體的類型,所以在1.5的時候引入了泛型,在聲明集合的時候就指定集合中存放的是什么類型的對象:
List<String> list = new ArrayList<String>();
使用泛型之后,在獲取時就不必擔心類型的問題,因為泛型在編譯時編譯器會檢查往集合中添加的對象的類型是否匹配泛型類型,如果不正確會在編譯時就會發現錯誤,而不必等到運行時才發現錯誤。
因為泛型是在1.5引入的,為了向前兼容,所以會在編譯時去掉泛型(泛型擦除),產生橋接方法。
總結
橋接方法的產生:
- 實現 泛型接口 的類,編譯器會為 這個類生成 橋接方法
- 繼承 超類的 方法,並且升級方法的返回類型(即子類 覆寫 超類方法 時,返回類型 升級為 原返回類型的父類)
為什么需要生成橋接方法?
- 因為泛型是在1.5引入的,為了向前兼容,所以會在編譯時去掉泛型(泛型擦除),產生橋接方法
參考文獻
java中什么是bridge method(橋接方法)
橋接方法的 access_flag
Java Language Specification