欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java實(shí)現(xiàn)橋接方法isBridge()和合成方法isSynthetic()

 更新時間:2023年06月06日 11:48:57   作者:秋官  
本文主要介紹了Java實(shí)現(xiàn)橋接方法isBridge()和合成方法isSynthetic(),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

今天在看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)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java實(shí)現(xiàn)不同的類的屬性之間相互賦值

    Java實(shí)現(xiàn)不同的類的屬性之間相互賦值

    今天小編就為大家分享一篇關(guān)于Java實(shí)現(xiàn)不同的類的屬性之間相互賦值,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-03-03
  • SpringBoot中的配置類(@Configuration)

    SpringBoot中的配置類(@Configuration)

    這篇文章主要介紹了SpringBoot中的配置類(@Configuration),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • Kafka中的producer攔截器與consumer攔截器詳解

    Kafka中的producer攔截器與consumer攔截器詳解

    這篇文章主要介紹了Kafka中的producer攔截器與consumer攔截器詳解,Producer 的Interceptor使得用戶在消息發(fā)送前以及Producer回調(diào)邏輯前有機(jī)會對消息做 一些定制化需求,比如修改消息等,需要的朋友可以參考下
    2023-12-12
  • springboot連接neo4j報錯的解決方案

    springboot連接neo4j報錯的解決方案

    這篇文章主要介紹了springboot連接neo4j報錯的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-02-02
  • SpringBoot項(xiàng)目創(chuàng)建單元測試的流程步驟

    SpringBoot項(xiàng)目創(chuàng)建單元測試的流程步驟

    在日常開發(fā)的過程中,對自己的代碼進(jìn)行單元測試是個非常重要的過程,一方面可以最小范圍的針對一個方法進(jìn)行測試,提高測試的簡便性以及測試的成本,本篇文章主要是為了總結(jié)一下如何優(yōu)雅的在Springboot項(xiàng)目中使用單元測試去測試功能,需要的朋友可以參考下
    2024-11-11
  • JAVA 線程通信相關(guān)知識匯總

    JAVA 線程通信相關(guān)知識匯總

    這篇文章主要介紹了JAVA 線程通信相關(guān)知識,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-06-06
  • WeakHashMap?和?HashMap?區(qū)別及使用場景

    WeakHashMap?和?HashMap?區(qū)別及使用場景

    這篇文章主要為大家介紹了WeakHashMap?和?HashMap?的區(qū)別是什么以及何時使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • Java中分割字符串的兩種方法實(shí)例詳解

    Java中分割字符串的兩種方法實(shí)例詳解

    這篇文章主要介紹了Java中分割字符串的兩種方法,一種是java.lang.String 的 split() 方法,,另外一種是用String Tokenizer類。文中的每種方法都給出了詳細(xì)的示例代碼,相信對大家的理解和學(xué)習(xí)具有一定的參考借鑒價值,有需要的朋友們下面來一起看看吧。
    2016-12-12
  • 解決SpringBoot在IDEA中熱部署失效問題

    解決SpringBoot在IDEA中熱部署失效問題

    熱部署是指程序運(yùn)行過程中實(shí)時更新或替換其組件的技術(shù),即項(xiàng)目正在啟動中,修改了配置文件中某個值或者添加了某個方法或者修改了某個方法參數(shù),本文給大家介紹了解決SpringBoot在IDEA中熱部署失效問題,需要的朋友可以參考下
    2024-01-01
  • Java Spring MVC 上傳下載文件配置及controller方法詳解

    Java Spring MVC 上傳下載文件配置及controller方法詳解

    這篇文章主要介紹了Java Spring MVC 上傳下載文件配置及controller方法詳解,本文介紹的非常詳細(xì),具有參考借鑒價值,需要的朋友可以參考下
    2016-09-09

最新評論