3分鐘快速搞懂Java的橋接方法

什么是橋接方法?

Java中的橋接方法(Bridge Method)是一種為了實(shí)現(xiàn)某些Java語言特性而由編譯器自動(dòng)生成的方法锣尉。

我們可以通過Method類的isBridge方法來判斷一個(gè)方法是否是橋接方法。

在字節(jié)碼文件中决采,橋接方法會(huì)被標(biāo)記為ACC_BRIDGEACC_SYNTHETIC自沧,其中ACC_BRIDGE用于表示該方法是由編譯器產(chǎn)生的橋接方法,ACC_SYNTHETIC用于表示該方法是由編譯器自動(dòng)生成。

什么時(shí)候生成橋接方法拇厢?

為了實(shí)現(xiàn)哪些Java語言特性會(huì)生成橋接方法爱谁?最常見的兩種情況就是協(xié)變返回值類型類型擦除,因?yàn)樗鼈儗?dǎo)致了父類方法的參數(shù)和實(shí)際調(diào)用的方法參數(shù)類型不一致孝偎。下面我們通過兩個(gè)例子更好地理解一下访敌。

協(xié)變返回類型

協(xié)變返回類型是指子類方法的返回值類型不必嚴(yán)格等同于父類中被重寫的方法的返回值類型,而可以是更 "具體" 的類型衣盾。

在Java 1.5添加了對(duì)協(xié)變返回類型的支持寺旺,即子類重寫父類方法時(shí),返回的類型可以是子類方法返回類型的子類势决。下面看一個(gè)例子:

public class Parent {
    Number get() {
        return 1;
    }
}
public class Child extends Parent {

    @Override
    Integer get() {
        return 1;
    }
}

Child類重寫其父類Parent的get方法阻塑,Parent的get方法返回類型為Number,而Child類中g(shù)et方法返回類型為Integer徽龟。

將這段代碼進(jìn)行編譯叮姑,再反編譯:

javac Child.java
javap -v -c Child.class

結(jié)果如下:

public class Child extends Parent
......省略部分結(jié)果......
  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

從上面的結(jié)果可以看到,有一個(gè)方法java.lang.Number get(), 在源碼中是沒有出現(xiàn)過的据悔,是由編譯器自動(dòng)生成的传透,該方法被標(biāo)記為ACC_BRIDGEACC_SYNTHETIC,就是我們前面所說的橋接方法极颓。

這個(gè)方法就起了一個(gè)橋接的作用朱盐,它所做的就是把對(duì)自身的調(diào)用通過invokevirtual指令再調(diào)用方法java.lang.Integer get()

編譯器這么做的原因是什么呢菠隆?因?yàn)樵贘VM方法中兵琳,返回類型也是方法簽名的一部分,而橋接方法的簽名和其父類的方法簽名一致骇径,以此就實(shí)現(xiàn)了協(xié)變返回值類型躯肌。

類型擦除

泛型是Java 1.5才引進(jìn)的概念,在這之前是沒有泛型的概念的破衔,但泛型代碼能夠很好地和之前版本的代碼很好地兼容清女,這是為什么呢?

這是因?yàn)槲福诰幾g期間Java編譯器會(huì)將類型參數(shù)替換為其上界(類型參數(shù)中extends子句的類型)嫡丙,如果上界沒有定義,則默認(rèn)為Object读第,這就叫做類型擦除曙博。

當(dāng)一個(gè)子類在繼承(或?qū)崿F(xiàn))一個(gè)父類(或接口)的泛型方法時(shí),在子類中明確指定了泛型類型怜瞒,那么在編譯時(shí)編譯器會(huì)自動(dòng)生成橋接方法父泳,例如:

public class Parent<T> {

    void set(T t) {
    }
}
public class Child extends Parent<String> {

    @Override
    void set(String str) {
    }
}

Child類在繼承其父類Parent的泛型方法時(shí),明確指定了泛型類型為String,將這段代碼進(jìn)行編譯尘吗,再反編譯:

public class Child extends Parent<java.lang.String>
......省略部分結(jié)果......
  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

從上面的結(jié)果可以看到逝她,有一個(gè)方法void set(java.lang.Object), 在源碼中是沒有出現(xiàn)過的,是由編譯器自動(dòng)生成的睬捶,該方法被標(biāo)記為ACC_BRIDGEACC_SYNTHETIC,就是我們前面所說的橋接方法近刘。

這個(gè)方法就起了一個(gè)橋接的作用擒贸,它所做的就是把對(duì)自身的調(diào)用通過invokevirtual指令再調(diào)用方法void set(java.lang.String)

編譯器這么做的原因是什么呢觉渴?因?yàn)镻arent類在類型擦除之后介劫,變成這樣:

public class Parent<Object> {

    void set(Object t) {
    }
}

編譯器為了讓子類有一個(gè)與父類的方法簽名一致的方法,就在子類自動(dòng)生成一個(gè)與父類的方法簽名一致的橋接方法案淋。

如何獲取橋接方法的實(shí)際方法

在Spring Framework中已經(jīng)實(shí)現(xiàn)了獲取橋接方法的實(shí)際方法的功能座韵,就在spring-core模塊中的BridgeMethodResolver類中,像這樣直接使用就行了:

method = BridgeMethodResolver.findBridgedMethod(method);

findBridgedMethod方法是怎么實(shí)現(xiàn)的呢踢京?我們來分析一下源碼(spring-core的版本為5.2.8.RELEASE):

public static Method findBridgedMethod(Method bridgeMethod) {
    // 如果不是橋連方法誉碴,就直接返回原方法。
    if (!bridgeMethod.isBridge()) {
        return bridgeMethod;
    }
    // 先從本地緩存讀取瓣距,緩存中有則直接返回黔帕。
    Method bridgedMethod = cache.get(bridgeMethod);
    if (bridgedMethod == null) {
        List<Method> candidateMethods = new ArrayList<>();
        // 以方法名稱和入?yún)€(gè)數(shù)相等為篩選條件。
        MethodFilter filter = candidateMethod ->
                isBridgedCandidateFor(candidateMethod, bridgeMethod);
        // 遞歸該類及其所有父類上的所有方法蹈丸,符合篩選條件就添加進(jìn)來成黄。
        ReflectionUtils.doWithMethods(bridgeMethod.getDeclaringClass()
            , candidateMethods::add, filter);
        if (!candidateMethods.isEmpty()) {
            // 如果符合篩選條件的方法個(gè)數(shù)為1,則直接采用逻杖;
            // 否則奋岁,調(diào)用searchCandidates方法再次篩選。
            bridgedMethod = candidateMethods.size() == 1 ?
                    candidateMethods.get(0) :
                    searchCandidates(candidateMethods, bridgeMethod);
        }
        // 如果找不到實(shí)際方法荸百,則返回原來的橋連方法闻伶。
        if (bridgedMethod == null) {
            // A bridge method was passed in but we couldn't find the bridged method.
            // Let's proceed with the passed-in method and hope for the best...
            bridgedMethod = bridgeMethod;
        }
        // 把查找的結(jié)果放入內(nèi)存緩存。
        cache.put(bridgeMethod, bridgedMethod);
    }
    return bridgedMethod;
}

我們?cè)倏匆幌略俅魏Y選的searchCandidates方法是如何實(shí)現(xiàn)的:

private static Method searchCandidates(List<Method> candidateMethods, Method bridgeMethod) {
    if (candidateMethods.isEmpty()) {
        return null;
    }
    Method previousMethod = null;
    boolean sameSig = true;
    // 遍歷候選方法的列表
    for (Method candidateMethod : candidateMethods) {
        // 對(duì)比橋接方法的泛型類型參數(shù)和候選方法是否匹配管搪,如果匹配則直接返回該候選方法虾攻。
        if (isBridgeMethodFor(bridgeMethod, candidateMethod, bridgeMethod.getDeclaringClass())) {
            return candidateMethod;
        }
        else if (previousMethod != null) {
            // 如果不匹配,則判斷所有候選方法的參數(shù)列表是否相等更鲁。
            sameSig = sameSig && Arrays.equals(candidateMethod.getGenericParameterTypes()
                , previousMethod.getGenericParameterTypes());
        }
        previousMethod = candidateMethod;
    }
    // 如果所有候選方法的參數(shù)列表全相等霎箍,則返回第一個(gè)候選方法。
    return (sameSig ? candidateMethods.get(0) : null);
}

總結(jié)以上源碼就是澡为,通過判斷方法名漂坏、參數(shù)的個(gè)數(shù)以及泛型類型參數(shù)來獲取橋接方法的實(shí)際方法。

最后,謝謝你這么帥顶别,還給我點(diǎn)贊關(guān)注谷徙。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市驯绎,隨后出現(xiàn)的幾起案子完慧,更是在濱河造成了極大的恐慌,老刑警劉巖剩失,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屈尼,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡拴孤,警方通過查閱死者的電腦和手機(jī)脾歧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來演熟,“玉大人鞭执,你說我怎么就攤上這事∶⒋猓” “怎么了兄纺?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)是辕。 經(jīng)常有香客問我囤热,道長(zhǎng),這世上最難降的妖魔是什么获三? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任旁蔼,我火速辦了婚禮,結(jié)果婚禮上疙教,老公的妹妹穿的比我還像新娘棺聊。我一直安慰自己,他們只是感情好贞谓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布限佩。 她就那樣靜靜地躺著,像睡著了一般裸弦。 火紅的嫁衣襯著肌膚如雪祟同。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天理疙,我揣著相機(jī)與錄音晕城,去河邊找鬼。 笑死窖贤,一個(gè)胖子當(dāng)著我的面吹牛砖顷,可吹牛的內(nèi)容都是我干的贰锁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼滤蝠,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼豌熄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起物咳,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤锣险,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后览闰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體囱持,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年焕济,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盔几。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晴弃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出逊拍,到底是詐尸還是另有隱情上鞠,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布芯丧,位于F島的核電站芍阎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏缨恒。R本人自食惡果不足惜谴咸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望骗露。 院中可真熱鬧岭佳,春花似錦、人聲如沸萧锉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)柿隙。三九已至叶洞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間禀崖,已是汗流浹背衩辟。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帆焕,地道東北人惭婿。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓不恭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親财饥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子换吧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容