Java系列:關於Java中的橋接方法


這兩天在看《Java核心技術 卷1》的泛型相關章節,其中說到了在泛型子類中override父類的泛型方法時,編譯器會自動生成一個橋接方法,這塊有點看不明白。

書上的例子代碼如下:

public class MyPair <T>{
    private T first;
    private T second;
    public MyPair(){ first = null; second = null;}
    public MyPair(T first, T second){ this.first = first; this.second = second;}
    public T getFirst(){ return first;}
    public T getSecond() {return second;}
    public void setFirst(T value){ first = value;}
    public void setSecond(T value) { second = value;}
}
public class DateInterval extends MyPair<Date> {
    public void setSecond(Date second) { 
        if(second.compareTo(getFirst()) >= 0)
            super.setSecond(second);
    }
}
public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub        
        DateInterval interval = new DateInterval();
        interval.setFirst(new Date());
        interval.setSecond(new Date());
        System.out.println("second value of interval: " + interval.getSecond().toString());
        Thread.sleep(10);
        MyPair<Date> datePair = interval;
        datePair.setSecond(new Date());
        System.out.println("second value of interval: " + datePair.getSecond().toString());
    }

 

一、通過jd-gui.exe來分析字節碼,只能看到類型擦除信息

上網查閱了一些資料還是不明白,然后覺得應該可以通過反編譯工具來看,於是找了jd-gui.exe來看,發現反編譯出來的東西和原始的類基本相同的,如下,關於書上提到的類型擦除倒是確實存在,可以看到在字節碼中其實沒有泛型,而是做了類型擦除之后的類型。

c53a2d46-ecca-45ce-91e1-e9d8d52f6979

 

public static void main(String[] args)
    throws InterruptedException
  {
    DateInterval interval = new DateInterval();
    interval.setFirst(new Date());
    interval.setSecond(new Date());
    System.out.println("second value of interval: " + ((Date)interval.getSecond()).toString());
    Thread.sleep(10L);
    MyPair<Date> datePair = interval;
    datePair.setSecond(new Date());
    System.out.println("second value of interval: " + ((Date)datePair.getSecond()).toString());
  }

二、用jclasslib來看字節碼

    感覺可能是jd-gui.exe太高級了,反編譯做過頭了,結果把我需要的信息都過濾掉了,所以找了稍微更原始反編譯工具,jclasslib,使用它打開DateInternal.class文件之后,可以看到如下信息。在上面的源碼中我們實際上只給DateInternal添加了一個setSecon方法,但是在反編譯之后發現可以看到兩個setSecond方法,兩個方法的信息分別如下。

1)第一個就是我們在源碼中定義的setSecond,入參為Date類型的setSecond方法;

f735ec46-a479-450d-be0d-24809d3966bf

2)第二個就是書上所說的橋接方法,可以看到這個方法的flag中,除了有public,還有bridge,synthetic兩個標志,這表示這個是由編譯器自動生成的橋接方法。

9fb68d8b-8ed7-4722-9d45-3f430bfd4dd9

3)在看看方法的內容,其實內部調用了DateInterval.setSecond方法,並且在

d7c41695-7476-49f8-bb60-242e7ef3d437

三、也可以使用javap命令來查看字節碼信息

在命令行輸入javap -c -v DateInternal.class

則會輸出如下信息,這里看到的信息和jclaslib看到的類似。

Classfile /D:/java/eclipse/learnJava/target/classes/me/ygc/javabasic/learnJava/DateInterval.class
  Last modified 2015-12-1; size 771 bytes
  MD5 checksum f8d67b651cd0aa143e3fbe03c5edd519
  Compiled from "DateInterval.java"
public class me.ygc.javabasic.learnJava.DateInterval extends me.ygc.javabasic.learnJava.MyPair<java.util.Date>
  minor version: 0
  major version: 49
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // me/ygc/javabasic/learnJava/DateInterval
   #2 = Utf8               me/ygc/javabasic/learnJava/DateInterval
   #3 = Class              #4             // me/ygc/javabasic/learnJava/MyPair
   #4 = Utf8               me/ygc/javabasic/learnJava/MyPair
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Methodref          #3.#9          // me/ygc/javabasic/learnJava/MyPair."<init>":()V
   #9 = NameAndType        #5:#6          // "<init>":()V
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lme/ygc/javabasic/learnJava/DateInterval;
  #14 = Utf8               setSecond
  #15 = Utf8               (Ljava/util/Date;)V
  #16 = Methodref          #1.#17         // me/ygc/javabasic/learnJava/DateInterval.getFirst:()Ljava/lang/Object;
  #17 = NameAndType        #18:#19        // getFirst:()Ljava/lang/Object;
  #18 = Utf8               getFirst
  #19 = Utf8               ()Ljava/lang/Object;
  #20 = Class              #21            // java/util/Date
  #21 = Utf8               java/util/Date
  #22 = Methodref          #20.#23        // java/util/Date.compareTo:(Ljava/util/Date;)I
  #23 = NameAndType        #24:#25        // compareTo:(Ljava/util/Date;)I
  #24 = Utf8               compareTo
  #25 = Utf8               (Ljava/util/Date;)I
  #26 = Methodref          #3.#27         // me/ygc/javabasic/learnJava/MyPair.setSecond:(Ljava/lang/Object;)V
  #27 = NameAndType        #14:#28        // setSecond:(Ljava/lang/Object;)V
  #28 = Utf8               (Ljava/lang/Object;)V
  #29 = Utf8               second
  #30 = Utf8               Ljava/util/Date;
  #31 = Methodref          #1.#32         // me/ygc/javabasic/learnJava/DateInterval.setSecond:(Ljava/util/Date;)V
  #32 = NameAndType        #14:#15        // setSecond:(Ljava/util/Date;)V
  #33 = Utf8               SourceFile
  #34 = Utf8               DateInterval.java
  #35 = Utf8               Signature
  #36 = Utf8               Lme/ygc/javabasic/learnJava/MyPair<Ljava/util/Date;>;
{
  public me.ygc.javabasic.learnJava.DateInterval();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #8                  // Method me/ygc/javabasic/learnJava/MyPair."<init>":()V
         4: return
      LineNumberTable:
        line 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lme/ygc/javabasic/learnJava/DateInterval;
  public void setSecond(java.util.Date);
    descriptor: (Ljava/util/Date;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_1
         1: aload_0
         2: invokevirtual #16                 // Method getFirst:()Ljava/lang/Object;
         5: checkcast     #20                 // class java/util/Date
         8: invokevirtual #22                 // Method java/util/Date.compareTo:(Ljava/util/Date;)I
        11: iflt          19
        14: aload_0
        15: aload_1
        16: invokespecial #26                 // Method me/ygc/javabasic/learnJava/MyPair.setSecond:(Ljava/lang/Object;)V
        19: return
      LineNumberTable:
        line 8: 0
        line 9: 14
        line 10: 19
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      20     0  this   Lme/ygc/javabasic/learnJava/DateInterval;
            0      20     1 second   Ljava/util/Date;
  public void setSecond(java.lang.Object);
    descriptor: (Ljava/lang/Object;)V
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: checkcast     #20                 // class java/util/Date
         5: invokevirtual #31                 // Method setSecond:(Ljava/util/Date;)V
         8: return
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
}
SourceFile: "DateInterval.java"
Signature: #36                          // Lme/ygc/javabasic/learnJava/MyPair<Ljava/util/Date;>;

四、通過代碼來驗證橋接方法的存在

如果編寫如下代碼:

public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub        
        MyPair datePair = new DateInterval();
        datePair.setSecond(new Object());
    }

運行之后會提示如下:

Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.util.Date

    at me.ygc.javabasic.learnJava.DateInterval.setSecond(DateInterval.java:1)

    at me.ygc.javabasic.learnJava.MyPair.main(MyPair.java:26)

說明他實際上是去調用了一個setSecond(Object)的方法,然后在內部做了從Object到Date的轉換,然后轉換失敗了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM