3分鐘快速搞懂Java的橋接方法
阿新 • • 發佈:2020-12-16
### 什麼是橋接方法?
Java中的橋接方法(Bridge Method)是一種為了實現某些Java語言特性而由編譯器自動生成的方法。
我們可以通過`Method`類的`isBridge`方法來判斷一個方法是否是橋接方法。
在位元組碼檔案中,橋接方法會被標記為`ACC_BRIDGE`和`ACC_SYNTHETIC`,其中`ACC_BRIDGE`用於表示該方法是由編譯器產生的橋接方法,`ACC_SYNTHETIC`用於表示該方法是由編譯器自動生成。
文章持續更新,微信搜尋「萬貓學社第一時間閱讀,關注後回覆「電子書」,免費獲取12本Java必讀技術書籍。
### 什麼時候生成橋接方法?
為了實現哪些Java語言特性會生成橋接方法?最常見的兩種情況就是**協變返回值型別**和**型別擦除**,因為它們導致了父類方法的引數和實際呼叫的方法引數型別不一致。下面我們通過兩個例子更好地理解一下。
文章持續更新,微信搜尋「萬貓學社第一時間閱讀,關注後回覆「電子書」,免費獲取12本Java必讀技術書籍。
#### 協變返回型別
協變返回型別是指子類方法的返回值型別不必嚴格等同於父類中被重寫的方法的返回值型別,而可以是更 "具體" 的型別。
在Java 1.5添加了對協變返回型別的支援,即子類重寫父類方法時,返回的型別可以是子類方法返回型別的子類。下面看一個例子:
```java
public class Parent {
Number get() {
return 1;
}
}
```
```java
public class Child extends Parent {
@Override
Integer get() {
return 1;
}
}
```
Child類重寫其父類Parent的get方法,Parent的get方法返回型別為Number,而Child類中get方法返回型別為Integer。
將這段程式碼進行編譯,再反編譯:
```
javac Child.java
javap -v -c Child.class
```
文章持續更新,微信搜尋「萬貓學社 第一時間閱讀,關注後回覆「電子書」,免費獲取12本Java必讀技術書籍。
結果如下:
```
public class Child extends Parent
......省略部分結果......
java.lang.Integer get();
descriptor: ()Ljava/lang/Integer;
flags:
Code:
stack=1, locals=1, args_size=1
0: iconst_1
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: areturn
LineNumberTable:
line 5: 0
java.lang.Number get();
descriptor: ()Ljava/lang/Number;
flags: ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokevirtual #3 // Method get:()Ljava/lang/Integer;
4: areturn
LineNumberTable:
line 1: 0
```
從上面的結果可以看到,有一個方法**java.lang.Number get()**, 在原始碼中是沒有出現過的,是由編譯器自動生成的,該方法被標記為`ACC_BRIDGE`和`ACC_SYNTHETIC`,就是我們前面所說的橋接方法。
這個方法就起了一個橋接的作用,它所做的就是把對自身的呼叫通過`invokevirtual`指令再呼叫方法**java.lang.Integer get()**。
**編譯器這麼做的原因是什麼呢?**因為在JVM方法中,返回型別也是方法簽名的一部分,而橋接方法的簽名和其父類的方法簽名一致,以此就實現了協變返回值型別。
文章持續更新,微信搜尋「萬貓學社 第一時間閱讀,關注後回覆「電子書」,免費獲取12本Java必讀技術書籍。
#### 型別擦除
泛型是Java 1.5才引進的概念,在這之前是沒有泛型的概念的,但泛型程式碼能夠很好地和之前版本的程式碼很好地相容,這是為什麼呢?
這是因為,在編譯期間Java編譯器會將型別引數替換為其上界(型別引數中extends子句的型別),如果上界沒有定義,則預設為Object,這就叫做型別擦除。
當一個子類在繼承(或實現)一個父類(或介面)的泛型方法時,在子類中明確指定了泛型型別,那麼在編譯時編譯器會自動生成橋接方法,例如:
```java
public class Parent {
void set(T t) {
}
}
```
```java
public class Child extends Parent {
@Override
void set(String str) {
}
}
```
文章持續更新,微信搜尋「萬貓學社 第一時間閱讀,關注後回覆「電子書」,免費獲取12本Java必讀技術書籍。
Child類在繼承其父類Parent的泛型方法時,明確指定了泛型型別為String,將這段程式碼進行編譯,再反編譯:
```
public class Child extends Parent
......省略部分結果......
void set(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags:
Code:
stack=0, locals=2, args_size=2
0: return
LineNumberTable:
line 5: 0
void set(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: 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 set:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 1: 0
```
從上面的結果可以看到,有一個方法**void set(java.lang.Object)**, 在原始碼中是沒有出現過的,是由編譯器自動生成的,該方法被標記為`ACC_BRIDGE`和`ACC_SYNTHETIC`,就是我們前面所說的橋接方法。
這個方法就起了一個橋接的作用,它所做的就是把對自身的呼叫通過`invokevirtual`指令再呼叫方法**void set(java.lang.String)**。
**編譯器這麼做的原因是什麼呢?**因為Parent類在型別擦除之後,變成這樣:
```java
public class Parent