激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

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

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

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

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

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

筆者在最近的日常工作中,因業務需要,研究 Java 字節碼層面的知識。具體是,需要根據類字節碼,獲取特定方法名的方法入參,此方法名在源碼中只有一個。但是在實際使用中發現:在類實現泛型接口的情況下,在字節碼層面,類卻有兩個同名方法,導致無法確定哪個方法才是我們需要的方法。經過研究發現,其中一個方法是編譯器在編譯的過程中,自動生成的橋接方法(bridge method),兩個方法可通過特定標識區分。

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

問題描述

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

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

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

在實際業務場景中,我們會有不同的操作算子,實現Operator 接口,進行業務邏輯處理。那么我們來創建一個具體的算子,并實現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 } }

其中,泛型接口中的入參類型 T,在實現類中替換成了實際需要的類型 java.lang.String。到這里,我們就準備好了代碼樣例。

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

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

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

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

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

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

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

圖 debug 發現 UserInfoOperator 類的兩個 process 方法

產生原因

編譯器生成 bridge 方法

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

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

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

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

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

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

圖 jclasslib 查看 Operator 類的字節碼

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

查閱相關資料后發現,標識符 synthetic ,表示此方法是否是由編譯器自動產生的;標識符 bridge,表示此方法是否是由編譯器產生的橋接方法。

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

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

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

為何生成 bridge 方法

正確編譯

在源碼中,Operator 類的 process 方法的參數定義是 process(T t),參數類型是 T。而在字節碼層面我們看到,process 方法在編譯之后,編譯器將入參類型變成了 java.lang.Object。偽代碼示意,大概是這樣:

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

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

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

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

類型擦除

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

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

這種情況下,編譯器同樣會產生一個橋接方法。方法簽名為 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); } }

因此,當我們使用如下方式進行比較的時候,能夠通過編譯并得到我們預期的結果:

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

解決方案

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

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

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

深入分析

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

類繼承

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

/**  * 如下會產生橋接方法嗎?  * @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() { } } }

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

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

圖 類C 生成橋接方法

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

圖 類D 沒有生成橋接方法

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

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

方法重寫

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

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

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 改變為它的子類 java.lang.String。編譯之后,我們同樣使用 jclasslib 插件,查看兩個類的字節碼,如下所示:

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

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

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

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

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

圖 Father類字節碼test() 方法

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

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

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

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

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.

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

參考

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

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产午夜精品一区二区三区在线观看 | 国产激情视频在线 | 热99re久久免费视精品频软件 | 久久恋 | 成人在线观看免费爱爱 | 国产九色91 | 国产1区2区3区中文字幕 | 国产在线a| 国产永久免费观看 | 毛片a区 | www视频免费在线观看 | 欧美一级理论 | 羞羞视频入口 | av电影院在线观看 | 欧美高清第一页 | 成年人黄色免费网站 | 日韩黄色三级视频 | 中文字幕免费看 | 久久久久久久久久久综合 | 国产成人精品日本亚洲语音 | 久久久无码精品亚洲日韩按摩 | 久久亚洲国产精品 | 成人精品久久久 | 成人免费福利视频 | 欧美成人精品一级 | 中文字幕视频在线播放 | 黄色特级一级片 | 蜜桃av鲁一鲁一鲁一鲁 | 一级黄色免费 | 男女羞羞视频在线观看免费 | 国产精品一区二区x88av | 国产精品观看在线亚洲人成网 | 亚洲人成中文字幕在线观看 | 亚洲国产一区二区三区 | 草久在线| 末成年女av片一区二区 | 蜜桃传免费看片www 日本一区二区三区视频在线 | 欧美一区二区三区中文字幕 | 成人在线免费视频播放 | 亚洲一区二区三区精品在线观看 | 成人免费淫片视频观 |