一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - Java教程 - Java底層知識:什么是 “橋接方法” ?

Java底層知識:什么是 “橋接方法” ?

2022-03-03 22:52程序員小灰小志 Java教程

筆者羅列了幾種編譯器為我們自動生成橋接方法的情況。那么是否還有其他場景下,編譯器也會生成橋接方法呢?

筆者在最近的日常工作中,因業(yè)務(wù)需要,研究 Java 字節(jié)碼層面的知識。具體是,需要根據(jù)類字節(jié)碼,獲取特定方法名的方法入?yún)ⅲ朔椒谠创a中只有一個。但是在實際使用中發(fā)現(xiàn):在類實現(xiàn)泛型接口的情況下,在字節(jié)碼層面,類卻有兩個同名方法,導(dǎo)致無法確定哪個方法才是我們需要的方法。經(jīng)過研究發(fā)現(xiàn),其中一個方法是編譯器在編譯的過程中,自動生成的橋接方法(bridge method),兩個方法可通過特定標(biāo)識區(qū)分。

注:此處的橋接方法,跟設(shè)計模式中的橋接模式,不是一個概念。

問題描述

為了能夠說明問題,筆者模糊了實際業(yè)務(wù)場景的具體案例,用一個稍微簡單,能夠說明問題的示例,來分析編譯器自動生成的橋接方法(bridge method)。

我們知道,Java 泛型是JDK 5 中引入的一個新特性,應(yīng)用廣泛。比如,我們有一個操作算子泛型接口 Operator,接口中有一個 process(T t) 方法,其作用是對入?yún)?T 進(jìn)行邏輯處理。示例代碼如下:

/**  * @author renzhiqiang  * @date 2022/2/20 18:30  */ public interface Operator<T> { /**  * process method  * @param t  */ void process(T t); }

在實際業(yè)務(wù)場景中,我們會有不同的操作算子,實現(xiàn)Operator 接口,進(jìn)行業(yè)務(wù)邏輯處理。那么我們來創(chuàng)建一個具體的算子,并實現(xiàn)Operator 接口,重寫 process(T t) 方法。如下:

/**  * 用戶信息算子  * @author renzhiqiang  * @date 2022/2/20 18:30  */ public class UserInfoOperator implements Operator<String> { @Override
    public void process(String s) { // do something } }

其中,泛型接口中的入?yún)㈩愋?T,在實現(xiàn)類中替換成了實際需要的類型 java.lang.String。到這里,我們就準(zhǔn)備好了代碼樣例。

那么,我們的目標(biāo)是什么呢?就是要獲取UserInfoOperator#process(String s) 方法的參數(shù)類型java.lang.String。讀到這里,讀者可能會想:這不很簡單么,通過反射,根據(jù)Class#getDeclaredMethods(),獲取到 UserInfoOperator 的所有方法,再找到方法名是 process 的方法,然后再獲取到參數(shù)列表,不就可以獲取參數(shù)類型java.lang.String 了么。

如果正在閱讀文章的你也這么想的話,那請繼續(xù)往下看。

根據(jù) Java 反射方法Class#getDeclaredMethods() 的描述:

Returns an array of Method objectsincluding public, protected, default (package) access, and private methods, butexcludes inherited methods.

翻譯過來就是:返回方法對象數(shù)組,包括公共方法、受保護(hù)方法、默認(rèn)(包)訪問方法和私有方法,但不包括繼承方法。

根據(jù)我們的示例,如果我們通過反射,利用Class#getDeclaredMethods() 方法,我們預(yù)期的返回方法數(shù)組中,應(yīng)該只有一個方法名是process 才對,但是這里卻有兩個 process 方法。驚不驚奇,意不意外!

Java底層知識:什么是 “橋接方法” ?

圖 debug 發(fā)現(xiàn) UserInfoOperator 類的兩個 process 方法

產(chǎn)生原因

編譯器生成 bridge 方法

我們知道,Java 源碼需要經(jīng)過編譯器編譯,生成對應(yīng)的 .class 文件,才能給 JVM 使用。在源碼中,我們只定義了一個名為 process 的方法。那么我們考慮,編譯器在編譯源碼的過程中,是否會進(jìn)行一些特的處理。為了更加直觀的查看編譯后的字節(jié)碼文件,在 Idea 安裝 jclasslib 插件,通過 jclasslib 查看 UserInfoOperator 和 Operator 的字節(jié)碼。如下:

Java底層知識:什么是 “橋接方法” ?

圖 jclasslib 查看 UserInfoOperator 類的字節(jié)碼(第一個 process 方法)

Java底層知識:什么是 “橋接方法” ?

圖 jclasslib 查看 UserInfoOperator 類的字節(jié)碼 (第二個 process 方法)

Java底層知識:什么是 “橋接方法” ?

圖 jclasslib 查看 Operator 類的字節(jié)碼

通過 jclasslib 查看 .class 文件發(fā)現(xiàn),在 UserInfoOperator 類中確實存在兩個 process 方法:其中一個方法入?yún)⑹?java.lang.String,另一個方法的入?yún)⑹?java.lang.Object。而在 Operator 字節(jié)碼中,只有一個 process 方法,方法的入?yún)⑹?java.lang.Object。同時我們注意到,在 UserInfoOperator 類的字節(jié)碼中, [訪問標(biāo)志]項,其中一個方法的訪問標(biāo)志是 [public synthetic bridge]。其中 public 很好理解,但是其中的 [synthetic bridge] 是怎么來的呢?

查閱相關(guān)資料后發(fā)現(xiàn),標(biāo)識符 synthetic ,表示此方法是否是由編譯器自動產(chǎn)生的;標(biāo)識符 bridge,表示此方法是否是由編譯器產(chǎn)生的橋接方法。

Java底層知識:什么是 “橋接方法” ?

圖 方法訪問標(biāo)志(來源:深入理解 Java 虛擬機(jī)(第三版))

到此,可以確定的是,其中一個process 方法,是編譯器自動產(chǎn)生的橋接方法。那么為什么編譯器會產(chǎn)生橋接方法呢?以及在什么情況下,會產(chǎn)生橋接方法?以及如何判斷一個方法是不是橋接方法?我們繼續(xù)往下分析。

為何生成 bridge 方法

正確編譯

在源碼中,Operator 類的 process 方法的參數(shù)定義是 process(T t),參數(shù)類型是 T。而在字節(jié)碼層面我們看到,process 方法在編譯之后,編譯器將入?yún)㈩愋妥兂闪?java.lang.Object。偽代碼示意,大概是這樣:

public interface Operator<Object> { /**  * 方法參數(shù)變成 Object 類型  * @param object  */ void process(Object object); }

想象一下,如果沒有編譯器自動生成的橋接方法,那么在編譯層面是不能通過的:因為接口 Operator 中的 process 方法,,經(jīng)過編譯之后,參數(shù)類型變成了 java.lang.Object 類型,而實現(xiàn)類 UserInfoOperator 中的 process 方法的參數(shù)是 java.lang.String 類型,兩者的方法參數(shù)不一致,導(dǎo)致UserInfoOperator 并沒有重寫接口中的 process 方法,因此編譯無法通過。

這種情況下,編譯器自動生成一個橋接方法 void process(Object obj) 方法,則可以編譯通過,似乎是理所當(dāng)然的事情。自動生成的 process方法,方法簽名為:void process(Object object)。偽代碼示意,大概是這樣:

// 自動生成的process 方法
public void process(Object object) { process((String) object); }

類型擦除

我們知道,Java 中的泛型在編譯期間會將泛型信息擦除。如代碼定義 List 和 List,編譯之后都會變成 List。我們再考慮一種常見的情形:Java 類庫中比較器的用法。我們自定義比較器的時候,可以通過實現(xiàn) Comparator 接口,實現(xiàn)比較邏輯。示例代碼如下:

public class MyComparator implements Comparator<Integer> { public int compare(Integer a,Integer b) { // 比較邏輯 } }

這種情況下,編譯器同樣會產(chǎn)生一個橋接方法。方法簽名為 intcompare(Object a, Object b) 。

Java底層知識:什么是 “橋接方法” ?

圖 MyComparator 類的兩個 compare 方法

偽代碼示意,大概是這樣:

public class MyComparator implements Comparator<Integer> { public int compare(Integer a,Integer b) { // 比較邏輯 } // 橋接方法 (bridge method) public int compare(Object a,Object b) { return compare((Integer)a,(Integer)b); } }

因此,當(dāng)我們使用如下方式進(jìn)行比較的時候,能夠通過編譯并得到我們預(yù)期的結(jié)果:

Object a = 5; Object b = 6; Comparator rawComp = new MyComparator(); // 可以通過編譯,因為自動生成了橋接方法compare(Object a, Object b) int comp = rawComp.compare(a, b);

另外,我們知道,泛型編譯之后,類型信息會被擦除。如果我們有這樣一個比較方法:

// 比較方法
public <T> T max(List<T> list, Comparator<T> comparator){ T biggestSoFar = list.get(0); for ( T t : list ) { if (comparator.compare(t,biggestSoFar) > 0) { biggestSoFar = t; } } return biggestSoFar; }

編譯之后,泛型被擦除掉,偽代碼表示,大概是這樣:

public Object max(List list, Comparator comparator) { Object biggestSoFar =list.get(0); for ( Object  t : list ) { if (comparator.compare(t,biggestSoFar) > 0) { //比較邏輯
          biggestSoFar = t; } } return biggestSoFar; }

我們將 MyComparator 其中一個參數(shù)傳入 max() 方法。如果沒有橋接方法的話,那么第四行的比較邏輯,將無法正確編譯,因為MyComparator 類中沒有兩個參數(shù)是 Object 類型的比較方法,只有參數(shù)類型是 Integer 類型的比較方法。讀者可自行測試。

解決方案

通過以上的案例描述,我們知道,在實現(xiàn)泛型接口的場景下,編譯器會自動生成橋接方法,保證編譯能夠通過。那么在這種情況下,我們只要識別哪一個是橋接方法,哪一個不是橋接方法,就可以解決我們一開始的問題。很自然的,既然編譯器自動產(chǎn)生了一個橋接方法,那么應(yīng)該會有某種方式,可以讓我們判斷一個方法是否是橋接方法。

果然,我們繼續(xù)研究發(fā)現(xiàn),Method 類中提供了 Method#isBridge() 方法。查看源碼中對方法的描述:Method#isBridge():Returns true if this method is a bridge method;returns false otherwise。

到此,我們通過反射,獲取到 UserInfoOperator 類中的兩個process 方法,再調(diào)用 Method#isBridge() 方法,即可鎖定需要的方法,因而進(jìn)一步獲取方法參數(shù) java.lang.String。

深入分析

至此可以說,就業(yè)務(wù)需求來說,我們完美的找到了解決方案。但在此之后,不禁會想:除了上述示例,還有哪些情況下,編譯器也會自動生成橋接方法呢?我們繼續(xù)深入研究。

類繼承

通過查閱相關(guān)資料,我們考慮如下一種情況:

/**  * 如下會產(chǎn)生橋接方法嗎?  * @author renzhiqiang  * @date 2022/2/20 18:33  */ public class BridgeMethodSample { static class A { public void foo() { } } public static class C extends A{ } public static class D extends A{ @Override
        public void foo() { } } }

上述代碼示例中,我們定義了三個靜態(tài)內(nèi)部類:A C D,其中 C D 分別繼承 A。經(jīng)過編譯,通過jclasslib 查看 BridgeMethodSample 字節(jié)碼,我們也發(fā)現(xiàn):類 C 中編譯器為其生成了橋接方法 void foo(),而類 D 中卻沒有。

Java底層知識:什么是 “橋接方法” ?

圖 類C 生成橋接方法

Java底層知識:什么是 “橋接方法” ?

圖 類D 沒有生成橋接方法

深入分析,并根據(jù)上述分析的經(jīng)驗,我們猜測,編譯器生成橋接方法,一定是在某種情況下需要一個方法,來滿足 Java 編程規(guī)范,或者需要保證程序運行的正確性。通過字節(jié)碼可以看出,類 A 沒有 public 修飾,包范圍以外的程序是沒有訪問類 A 的權(quán)限的,更不用說類 A 中的方法。

但是類 C 是有public 修飾,C 類中的方法,包括繼承來的方法,是可以被包外的程序訪問的。因此,編譯器需要生成一個橋接方法,以保證能夠訪問 foo() 方法,滿足程序的正確運行。但是,類 D 同樣繼承 A,卻沒有生成橋接方法,根本原因是類 D 中重寫了父類 A 中的 foo() 方法,即沒有必要生成橋接方法。

方法重寫

我們再看一種情況,方法重寫。

Java 中,方法重寫(Override),是子類對父類的允許訪問的方法的實現(xiàn)過程進(jìn)行重新編寫的過程。重寫需要滿足一定的規(guī)則:

1. The method must have the same name as in the parentclass.

2. The method must have the same parameter as in theparent class.

3. There must be an IS-A relationship (inheritance).

JDK 5 之后,重寫方法的返回類型,可以與父類方法返回類型相同,也可以不相同,但必須是父類方法返回類型的子類。我們考慮如下代碼示例:

// 定義一個父類,包含一個 test() 方法
public class Father { public Object test(String s) { return s; } } // 定義一個子類,繼承父類
public class Child extends Father { @Override
    public String test(String s) { return s; } }

以上,在 Child 子類中,我們重寫了 test() 方法,但是返回值的類型,我們將 java.lang.Object 改變?yōu)樗淖宇?java.lang.String。編譯之后,我們同樣使用 jclasslib 插件,查看兩個類的字節(jié)碼,如下所示:

Java底層知識:什么是 “橋接方法” ?

圖 Child 類字節(jié)碼test() 方法(1)

Java底層知識:什么是 “橋接方法” ?

圖 Child 類字節(jié)碼test() 方法(2)

Java底層知識:什么是 “橋接方法” ?

圖 Father類字節(jié)碼test() 方法

根據(jù)上圖我們發(fā)現(xiàn),Child 類中我們重寫了 test() 方法,但是在字節(jié)碼層面,發(fā)現(xiàn)有兩個 test() 方法,其中一個方法的訪問標(biāo)志為 [public synthetic bridge], 表示這個方法是編譯器為我們生成的。而當(dāng)我們不改變 Child#test() 方法的返回類型時,編譯器并沒有為我們生成橋接方法,讀者可自行試驗。

也就是說,在子類方法重寫父類方法,返回類型不一致的情況下,編譯器也為我們生成了橋接方法。

以上,筆者羅列了幾種編譯器為我們自動生成橋接方法的情況。那么是否還有其他場景下,編譯器也會生成橋接方法呢?如果您也曾研究過或者使用過 bridge 方法,歡迎交流討論。

同時,給出一個 bridge 方法的非官方定義,希望能夠給讀者一些啟發(fā):

Bridge Method: These are methods that create an intermediate layerbetween the source and the target functions. It is usually used as part of thetype erasure process. It means that the bridge method is required as a typesafe interface.

限于筆者水平有限,難免有理解不準(zhǔn)確、不到位的地方。歡迎交流討論!

參考

https://stackoverflow.com/questions/5007357/java-generics-bridge-method

https://stackoverflow.com/questions/14144888/find-generic-method-with-actual-types-from-getdeclaredmethods

https://www.geeksforgeeks.org/method-class-isbridge-method-in-java/

原文地址:https://mp.weixin.qq.com/s/iqr8_PckhYKrKREE53ovBA

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 白丝h视频 | 好爽视频 | 成年男女免费视频观看性 | 狠狠做五月深爱婷婷天天综合 | 日本videos有奶水的hd | 欧洲肥女大肥臀tv | 成人免费观看在线视频 | 女人叉开腿让男人桶 | 校园春色偷拍自拍 | 涩情主播在线翻车 | 极品91| 先锋资源久久 | 国产视频99 | 免费十几分视频 | 亚洲精品久久久久69影院 | 日本中文字幕一区二区有码在线 | 6080午夜| 男人与禽交的方法 | 果冻传媒天美传媒在线小视频播放 | 免费看美女被靠到爽的视频 | 欧亚精品一区二区三区 | 9191精品国产观看 | 亚洲国产精品婷婷久久久久 | 四虎最新永久免费视频 | 日女人免费视频 | 精品一区二区三区视频 | 青草视频免费观看在线观看 | 男生操女生漫画 | 亚洲第一福利网 | 欧美精品一区二区三区免费播放 | 天堂伊人 | 久久青青草视频在线观 | 97综合| 五月色综合婷婷综合俺来也 | 国产清纯女高中生在线观看 | 四虎精品永久在线网址 | 四虎影视在线看 | 欧美一区二区视频 | 亚欧毛片基地国产毛片基地 | 亚洲AV无码国产精品色午夜情 | 香蕉久久综合 |