這兩天在看《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來看,發現反編譯出來的東西和原始的類基本相同的,如下,關於書上提到的類型擦除倒是確實存在,可以看到在字節碼中其實沒有泛型,而是做了類型擦除之后的類型。
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方法;
2)第二個就是書上所說的橋接方法,可以看到這個方法的flag中,除了有public,還有bridge,synthetic兩個標志,這表示這個是由編譯器自動生成的橋接方法。
3)在看看方法的內容,其實內部調用了DateInterval.setSecond方法,並且在
三、也可以使用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的轉換,然后轉換失敗了。