Java實(shí)現(xiàn)橋接方法isBridge()和合成方法isSynthetic()
今天在看spring的時候看到這樣一段代碼
public abstract class ReflectionUtils { ? ? public static final MethodFilter USER_DECLARED_METHODS = ? ? ? ? ? (method -> !method.isBridge() && !method.isSynthetic()); } ? ?
其中 isBridge() 和 isSynthetic() 分別用來判斷方法是否為橋接方法和合成方法,那么接下來我們就看下他倆到底有什么作用?
1.橋接方法
橋接方法是在jdk5引入泛型后,為了使泛型方法生成的字節(jié)碼和之前的版本相兼容,而由編譯器自動生成的方法。
編譯器是在什么時候會生成橋接方法呢?這個在官方的JLS中也有說明,可以具體看下。
當(dāng)子類在繼承(或?qū)崿F(xiàn))一個帶有泛型的父類(或接口)時,在子類中明確指定了泛型,此時編譯器在編譯時就會自動生成橋接方法。
1.1 從字節(jié)碼看橋接方法
我們通過一段代碼來看下:
//接口 public interface Action<T> { ? ? T play(T action); }
//實(shí)現(xiàn)類 public class Children implements Action<String> { ? ? @Override ? ? public String play(String action) { ? ? ? ? return "play basketball....."; ? ? } }
我們將實(shí)現(xiàn)類Children編譯看下字節(jié)碼:
Compiled from "Children.java" public class com.qiuguan.juc.bridge.Children extends java.lang.Object implements com.qiuguan.juc.bridge.Action<java.lang.String> { ? public com.qiuguan.juc.bridge.Children(); ? ? descriptor: ()V ? ? flags: ACC_PUBLIC ? ? Code: ? ? ? stack=1, locals=1, args_size=1 ? ? ? ? ?0: aload_0 ? ? ? ? ?1: invokespecial #1 ? ? ? ? ? ? ? ? ?// Method java/lang/Object."<init>":()V ? ? ? ? ?4: return ? ? ? LineNumberTable: ? ? ? ? line 7: 0 ? ? ? LocalVariableTable: ? ? ? ? Start ?Length ?Slot ?Name ? Signature ? ? ? ? ? ? 0 ? ? ? 5 ? ? 0 ?this ? Lcom/qiuguan/juc/bridge/Children; ? public java.lang.String play(java.lang.String); ? ? descriptor: (Ljava/lang/String;)Ljava/lang/String; ? ? flags: ACC_PUBLIC ? ? Code: ? ? ? stack=1, locals=2, args_size=2 ? ? ? ? ?0: ldc ? ? ? ? ? #2 ? ? ? ? ? ? ? ? ?// String play basketball..... ? ? ? ? ?2: areturn ? ? ? LineNumberTable: ? ? ? ? line 11: 0 ? ? ? LocalVariableTable: ? ? ? ? Start ?Length ?Slot ?Name ? Signature ? ? ? ? ? ? 0 ? ? ? 3 ? ? 0 ?this ? Lcom/qiuguan/juc/bridge/Children; ? ? ? ? ? ? 0 ? ? ? 3 ? ? 1 action ? Ljava/lang/String; ? //這個方法我們并沒有定義,這個就是編譯器自動生成的橋接方法 ? public java.lang.Object play(java.lang.Object); ? ? descriptor: (Ljava/lang/Object;)Ljava/lang/Object; ? ? flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC ? ? Code: ? ? ? stack=2, locals=2, args_size=2 ? ? ? ? ?0: aload_0 ? ? ? ? ?1: aload_1 ? ? ? ? ?2: checkcast ? ? #3 ? ? ? ? ? ? ? ? ?// class java/lang/String ? ? ? ? ?5: invokevirtual #4 ? ? ? ? ? ? ? ? ?// Method play:(Ljava/lang/String;)Ljava/lang/String; ? ? ? ? ?8: areturn ? ? ? LineNumberTable: ? ? ? ? line 7: 0 ? ? ? LocalVariableTable: ? ? ? ? Start ?Length ?Slot ?Name ? Signature ? ? ? ? ? ? 0 ? ? ? 9 ? ? 0 ?this ? Lcom/qiuguan/juc/bridge/Children; } Signature: #21 ? ? ? ? ? ? ? ? ? ? ? ? ?// Ljava/lang/Object;Lcom/qiuguan/juc/bridge/Action<Ljava/lang/String;>; SourceFile: "Children.java"
從字節(jié)碼中可以看到,一共有3個方法,第一個是無參構(gòu)造器,第二個是我們實(shí)現(xiàn)了接口的方法,而第三個就是編譯器生成的橋接方法,單獨(dú)看下這個橋接方法:
public java.lang.Object play(java.lang.Object); descriptor: (Ljava/lang/Object;)Ljava/lang/Object; //ACC_BRIDGE: 橋接方法的標(biāo)識 flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: aload_1 2: checkcast #3 // class java/lang/String 5: invokevirtual #4 // Method play:(Ljava/lang/String;)Ljava/lang/String; 8: areturn LineNumberTable: line 7: 0 LocalVariableTable: Start Length Slot Name Signature 0 9 0 this Lcom/qiuguan/juc/bridge/Children;
可以看到它含有一個 ACC_BRIDGE 的標(biāo)識,表明他是一個橋接方法,而且他的返回值類型和參數(shù)類型都是java.lang.Object,從字節(jié)碼中的第9行可以看到,它會將Object轉(zhuǎn)成String類型,然后再調(diào)用Children類中聲明的方法。轉(zhuǎn)換一下就是
public Object play(Object object) { return this.play((String)object); }
所以說,橋接方法實(shí)際上調(diào)用了具體泛型的方法,看下下面的這段代碼:
public class Test { ? ? public static void main(String[] args) { ? ? ? ? //接口不指定泛型 ? ? ? ? Action children = new Children(); ? ? ? ? System.out.println(children.play("basketball")); ? ? ? ? System.out.println(children.play(new Object())); ? ? } }
父接口不指定泛型,那么在方法調(diào)用時就可以傳任何參數(shù),因?yàn)锳ction接口的方法參數(shù)實(shí)際上是Object類型,此時我傳String或者Object都可以,都不會報錯。在運(yùn)行時參數(shù)類型不是Children聲明的類型時,才會拋出類型轉(zhuǎn)換異常,上面的代碼輸出就是這樣:
play basketball.....
Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
at com.qiuguan.juc.bridge.Children.play(Children.java:7)
at com.qiuguan.juc.bridge.Test.main(Test.java:21)
如果我們再聲明 Action接口時指定泛型,比如:
Action<String> children = new Children();
當(dāng)然這里只能是String類型,因?yàn)镃hildren類的泛型類型就是String,如果指定其他類型,那么在編譯時就會報錯,這樣就把類型檢查從運(yùn)行時提前到了編譯時,這就是泛型的好處。
1.2 從反射看橋接方法
還是使用上面的例子,我們通過反射來看下:
public class Test { ? ? public static void main(String[] args) { ? ? ? ? Method[] declaredMethods = Children.class.getDeclaredMethods(); ? ? ? ? for (Method m : declaredMethods) { ? ? ? ? ? ? System.out.printf("methodName = %s , paramType = %s, returnType = %s, isBridge() = %s\n", m.getName(), Arrays.toString(m.getParameterTypes()), m.getReturnType(), m.isBridge()); ? ? ? ? } ? ? } }
我們看下運(yùn)行結(jié)果:
methodName = play , paramType = [class java.lang.String], returnType = class java.lang.String, isBridge() = false
methodName = play , paramType = [class java.lang.Object], returnType = class java.lang.Object, isBridge() = true
不難發(fā)現(xiàn),它確實(shí)存在兩個play方法,其中第二個就是編譯器生成的橋接方法。
1.3 為什么要生成橋接方法?
前面我們有說到 當(dāng)子類在繼承(或?qū)崿F(xiàn))一個帶有泛型的父類(或接口)時,在子類中明確指定了泛型,此時編譯器在編譯時就會自動生成橋接方法,其實(shí)說白了就是和泛型有關(guān)。我們知道泛型是JDK5引入了,在JDK5之前,聲明一個容器,我們一般會這樣:
List list = new ArrayList<>(); list.add("abc"); list.add(123); list.add(new Object()); list.add(0.3f);
往list容器中可以添加任何類型的對象,當(dāng)從容器中取數(shù)據(jù)時,由于不確定類型,所以需要我們手動的去判斷所需要的具體類型,在JDK5引入泛型后,我們就可以約定容器只能放什么類型的數(shù)據(jù)了:
List<String> list = new ArrayList(); list.add("abc");
這樣就不用擔(dān)心類型的問題了。但是泛型是在JDK5引入的,為了向下兼容,引入了泛型擦除的機(jī)制,在編譯時將泛型去掉,變成Object類型。也正是由于泛型擦除的特性,如果不生成橋接方法,那么就與之前的字節(jié)碼存在兼容性的問題了。
我們在回過頭來看下前面的Aicton接口的字節(jié)碼
Compiled from "Action.java" public interface com.qiuguan.juc.bridge.Action<T extends java.lang.Object> minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT Constant pool: #1 = Class #10 // com/qiuguan/juc/bridge/Action #2 = Class #11 // java/lang/Object #3 = Utf8 play #4 = Utf8 (Ljava/lang/Object;)Ljava/lang/Object; #5 = Utf8 Signature #6 = Utf8 (TT;)TT; #7 = Utf8 <T:Ljava/lang/Object;>Ljava/lang/Object; #8 = Utf8 SourceFile #9 = Utf8 Action.java #10 = Utf8 com/qiuguan/juc/bridge/Action #11 = Utf8 java/lang/Object { public abstract T play(T); descriptor: (Ljava/lang/Object;)Ljava/lang/Object; flags: ACC_PUBLIC, ACC_ABSTRACT Signature: #6 // (TT;)TT; } Signature: #7 // <T:Ljava/lang/Object;>Ljava/lang/Object; SourceFile: "Action.java"
通過 “Signature: #6” 和 “Signature: #7” 可以看到,在編譯完成后實(shí)際上就變成了Object類型了
java復(fù)制代碼public abstract Object play(Object action)
而Children實(shí)現(xiàn)了這個接口,如果不生成橋接方法,那么Children就沒有實(shí)現(xiàn)接口中定義的這個方法,語義就不正確了,所以編譯器才會自動生成橋接方法,來保證兼容性。
2.合成方法
我們還是通過例子來看什么是合成方法?,以及什么條件下會生成合成方法?
public class Animal { ? ? public static void main(String[] args) { ? ? ? ? Animal.Dog dog = new Animal.Dog(); ? ? ? ? //外部類訪問內(nèi)部類的私有屬性 ? ? ? ? System.out.println(dog.name); ? ? } ? ? //內(nèi)部類 ? ? private static class Dog { ? ? ? ? private String name = "旺財"; ? ? } }
我們將上面的代碼編譯一下,可以看到有3個文件
Animal$1.class // ?
Animal$Dog.class //內(nèi)部類
Animal.class //外部類
其中第一個類是做什么的?我們并沒有定義過,為什么會產(chǎn)生呢?先帶著疑問往下看,我們先看下內(nèi)部類的反編譯結(jié)果:
可以使用在線反編譯工具,或者用 javap -c Animal\$Dog.class 指令
import com.qiuguan.juc.bridge.Animal.1; class Animal$Dog { ? ?private String name; ? ?private Animal$Dog() { ? ? ? this.name = "旺財"; ? ?} ? ?//這是一個合成的構(gòu)造器 ? ?// $FF: synthetic method ? ?Animal$Dog(1 x0) { ? ? ? this(); ? ?} ? ?//這里生成了一個 access$100的方法,這個是什么? ? ?// $FF: synthetic method ? ?static String access$100(Animal$Dog x0) { ? ? ? return x0.name; ? ?} }
反編譯后,我們看到它生成了 access$100的方法,這個方法是干什么的?我們并沒有定義呀,為何會生成呢?我們還是繼續(xù)往下看:
在我上面舉的例子中,name是內(nèi)部類Dog的私有屬性,但是外部類卻直接引用了這個屬性,從語法結(jié)構(gòu)上好像沒有什么問題,但是從編譯器的角度看,這就有點(diǎn)麻煩了,實(shí)際上外部類和內(nèi)部類是平等的,就完全是兩個獨(dú)立的類,這種情況下,外部類直接引用內(nèi)部類的私有屬性,就有點(diǎn)為違背了封裝原則。
于是,編譯器就要做些什么,我們把外部類反編譯也看下
javap -c Animal.class
Compiled from "Animal.java" public class com.qiuguan.juc.bridge.Animal { ? public com.qiuguan.juc.bridge.Animal(); ? ? Code: ? ? ? ?0: aload_0 ? ? ? ?1: invokespecial #1 ? ? ? ? ? ? ? ? ?// Method java/lang/Object."<init>":()V ? ? ? ?4: return ? public static void main(java.lang.String[]); ? ? Code: ? ? ? ?0: new ? ? ? ? ? #2 ? ? ? ? ? ? ? ? ?// class com/qiuguan/juc/bridge/Animal$Dog ? ? ? ?3: dup ? ? ? ?4: aconst_null ? ? ? ?5: invokespecial #3 ? ? ? ? ? ? ? ? ?// Method com/qiuguan/juc/bridge/Animal$Dog."<init>":(Lcom/qiuguan/juc/bridge/Animal$1;)V ? ? ? ?8: astore_1 ? ? ? ?9: getstatic ? ? #4 ? ? ? ? ? ? ? ? ?// Field java/lang/System.out:Ljava/io/PrintStream; ? ? ? 12: aload_1 ? ? ? //重點(diǎn)看這里。。。。。。 ? ? ? 13: invokestatic ?#5 ? ? ? ? ? ? ? ? ?// Method com/qiuguan/juc/bridge/Animal$Dog.access$100:(Lcom/qiuguan/juc/bridge/Animal$Dog;)Ljava/lang/String; ? ? ? 16: invokevirtual #6 ? ? ? ? ? ? ? ? ?// Method java/io/PrintStream.println:(Ljava/lang/String;)V ? ? ? 19: return }
重點(diǎn)看第19行的指令,這里在源碼中就是輸出內(nèi)部類的name屬性,但是從字節(jié)碼中我們可以看到,它實(shí)際上調(diào)用了內(nèi)部類的 access$100方法,這個方法是不是比較熟悉了,上面我們剛看到的,這個方法是一個靜態(tài)方法,它返回的就是內(nèi)部類的私有屬性name。
現(xiàn)在知道外部類訪問內(nèi)部類的私有屬性,編譯器為我們做了什么了,接下來我們再繼續(xù)回過頭來看下,編譯后生成的第三個類 Animal\$1.class
//看著就是一個普通的類,不過他是編譯器生成的合成類。 // $FF: synthetic class class Animal$1 { }
這個類看起來就像是一個普通的類,只不過他是編譯器生成的一個合成類。
說白了,synthetic 就是突破限制繼而能夠訪問一些private的字段。尤其在這種內(nèi)部類的情況。
再舉一個在日常開發(fā)中也比較的枚舉
public enum ColorEnum { ? ? RED,BLACK,GREEN,BLUE; ? ? public ColorEnum getColorEnum(String name){ ? ? ? ? ColorEnum[] values = ColorEnum.values(); ? ? ? ? for (ColorEnum value : values) { ? ? ? ? ? ? if (value.name().equals(name)) { ? ? ? ? ? ? ? ? return value; ? ? ? ? ? ? } ? ? ? ? } ? ? ? ? return ColorEnum.RED; ? ? } }
借助在線工具反編譯后看下:
public enum ColorEnum { ? ?RED, ? ?BLACK, ? ?GREEN, ? ?BLUE; ? ?// $FF: synthetic field ? ?private static final ColorEnum[] $VALUES = new ColorEnum[]{RED, BLACK, GREEN, BLUE}; ? ?public ColorEnum getColorEnum(String name) { ? ? ? ColorEnum[] values = values(); ? ? ? ColorEnum[] var3 = values; ? ? ? int var4 = values.length; ? ? ? for(int var5 = 0; var5 < var4; ++var5) { ? ? ? ? ?ColorEnum value = var3[var5]; ? ? ? ? ?if(value.name().equals(name)) { ? ? ? ? ? ? return value; ? ? ? ? ?} ? ? ? } ? ? ? return RED; ? ?} }
可以看到,它內(nèi)部會生成一個合成屬性 $VALUES。
到此這篇關(guān)于Java實(shí)現(xiàn)橋接方法isBridge()和合成方法isSynthetic()的文章就介紹到這了,更多相關(guān)Java isBridge() isSynthetic()內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 使用Java橋接模式打破繼承束縛優(yōu)雅實(shí)現(xiàn)多維度變化
- Java結(jié)構(gòu)型模式之橋接模式詳解
- 詳解Java設(shè)計(jì)模式之橋接模式
- Java結(jié)構(gòu)型設(shè)計(jì)模式之橋接模式詳細(xì)講解
- 一文搞懂Java橋接方法
- 詳解Java設(shè)計(jì)模式之橋接模式
- Java設(shè)計(jì)模式以虹貓藍(lán)兔的故事講解橋接模式
- Java設(shè)計(jì)模式七大原則之合成復(fù)用原則詳解
- java面向?qū)ο笤O(shè)計(jì)原則之合成復(fù)用原則示例詳解
- java合成模式之神奇的樹結(jié)構(gòu)
相關(guān)文章
Java實(shí)現(xiàn)不同的類的屬性之間相互賦值
今天小編就為大家分享一篇關(guān)于Java實(shí)現(xiàn)不同的類的屬性之間相互賦值,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03SpringBoot中的配置類(@Configuration)
這篇文章主要介紹了SpringBoot中的配置類(@Configuration),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06Kafka中的producer攔截器與consumer攔截器詳解
這篇文章主要介紹了Kafka中的producer攔截器與consumer攔截器詳解,Producer 的Interceptor使得用戶在消息發(fā)送前以及Producer回調(diào)邏輯前有機(jī)會對消息做 一些定制化需求,比如修改消息等,需要的朋友可以參考下2023-12-12SpringBoot項(xiàng)目創(chuàng)建單元測試的流程步驟
在日常開發(fā)的過程中,對自己的代碼進(jìn)行單元測試是個非常重要的過程,一方面可以最小范圍的針對一個方法進(jìn)行測試,提高測試的簡便性以及測試的成本,本篇文章主要是為了總結(jié)一下如何優(yōu)雅的在Springboot項(xiàng)目中使用單元測試去測試功能,需要的朋友可以參考下2024-11-11WeakHashMap?和?HashMap?區(qū)別及使用場景
這篇文章主要為大家介紹了WeakHashMap?和?HashMap?的區(qū)別是什么以及何時使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Java Spring MVC 上傳下載文件配置及controller方法詳解
這篇文章主要介紹了Java Spring MVC 上傳下載文件配置及controller方法詳解,本文介紹的非常詳細(xì),具有參考借鑒價值,需要的朋友可以參考下2016-09-09