Java泛型理解

1.正常類

1.1 Bean類的源碼

public class Bean{
    private Object value;

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }
}

1.2編譯Bean,使用下面命令

    javac Bean.java

1.3查看Bean.class文件的字節碼,使用下面命令

   javap -v -c -s -p Bean.class

字節碼如下:

Classfile /home/user/test/java/Bean.class
  Last modified Dec 7, 2018; size 389 bytes
  MD5 checksum c5bca1bb442cc81cd762d547acfec8af
  Compiled from "Bean.java"
public class Bean
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#17         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#18         // Bean.value:Ljava/lang/Object;
   #3 = Class              #19            // Bean
   #4 = Class              #20            // java/lang/Object
   #5 = Utf8               value
   #6 = Utf8               Ljava/lang/Object;
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               getValue
  #12 = Utf8               ()Ljava/lang/Object;
  #13 = Utf8               setValue
  #14 = Utf8               (Ljava/lang/Object;)V
  #15 = Utf8               SourceFile
  #16 = Utf8               Bean.java
  #17 = NameAndType        #7:#8          // "<init>":()V
  #18 = NameAndType        #5:#6          // value:Ljava/lang/Object;
  #19 = Utf8               Bean
  #20 = Utf8               java/lang/Object
{
  private java.lang.Object value;
    descriptor: Ljava/lang/Object;
    flags: ACC_PRIVATE

  public Bean();
    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 5: 0

  public java.lang.Object getValue();
    descriptor: ()Ljava/lang/Object;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field value:Ljava/lang/Object;
         4: areturn
      LineNumberTable:
        line 9: 0

  public void setValue(java.lang.Object);
    descriptor: (Ljava/lang/Object;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #2                  // Field value:Ljava/lang/Object;
         5: return
      LineNumberTable:
        line 13: 0
        line 14: 5
}
SourceFile: "Bean.java"

2.泛型類

2.1GenericBean源代碼

public class GenericBean<T>{
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

2.2編譯GenericBean,使用如下命令

   javac GenericBean.java

2.3查看GenericBean.class的字節碼文件,使用如下命令

   javap -v -c -s -p GenericBean.class

字節碼如下:

Classfile /home/user/test/java/GenericBean.class
  Last modified Dec 7, 2018; size 513 bytes
  MD5 checksum 19b05e7b5859fca3fa0b5bfa35cfcfff
  Compiled from "GenericBean.java"
public class GenericBean<T extends java.lang.Object> extends java.lang.Object
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#22         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#23         // GenericBean.value:Ljava/lang/Object;
   #3 = Class              #24            // GenericBean
   #4 = Class              #25            // java/lang/Object
   #5 = Utf8               value
   #6 = Utf8               Ljava/lang/Object;
   #7 = Utf8               Signature
   #8 = Utf8               TT;
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               getValue
  #14 = Utf8               ()Ljava/lang/Object;
  #15 = Utf8               ()TT;
  #16 = Utf8               setValue
  #17 = Utf8               (Ljava/lang/Object;)V
  #18 = Utf8               (TT;)V
  #19 = Utf8               <T:Ljava/lang/Object;>Ljava/lang/Object;
  #20 = Utf8               SourceFile
  #21 = Utf8               GenericBean.java
  #22 = NameAndType        #9:#10         // "<init>":()V
  #23 = NameAndType        #5:#6          // value:Ljava/lang/Object;
  #24 = Utf8               GenericBean
  #25 = Utf8               java/lang/Object
{
  private T value;
    descriptor: Ljava/lang/Object;
    flags: ACC_PRIVATE
    Signature: #8                           // TT;

  public GenericBean();
    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 5: 0

  public T getValue();
    descriptor: ()Ljava/lang/Object;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field value:Ljava/lang/Object;
         4: areturn
      LineNumberTable:
        line 9: 0
    Signature: #15                          // ()TT;

  public void setValue(T);
    descriptor: (Ljava/lang/Object;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #2                  // Field value:Ljava/lang/Object;
         5: return
      LineNumberTable:
        line 13: 0
        line 14: 5
    Signature: #18                          // (TT;)V
}
Signature: #19                          // <T:Ljava/lang/Object;>Ljava/lang/Object;
SourceFile: "GenericBean.java"

3.字節碼區別

對比Bean.class與GenericBean.class字節碼,發現兩個文件字節碼差異很小,比較大的差異在於GenericBean.class的字節碼文件多了大概下面東西:

 Signature: #8                           // TT;
 Signature: #15                          // ()TT;
 Signature: #18                          // (TT;)V

4.測試類

4.1GenericTest類的源代碼

 public class GenericTest {
    public static void main(String[] args){
        GenericBean<Integer> bean1 = new GenericBean<Integer>();
        bean1.setValue(1);

        Integer value = bean1.getValue();
    }
}

4.2編譯GenericTest類,使用下面命令

     javac GenericTest.java

4.3查看GenericTest.class字節碼,命令如下

     javap -v -c -s -p GenericTest.class

字節碼如下:

Classfile /home/user/test/java/GenericTest.class
  Last modified Dec 7, 2018; size 482 bytes
  MD5 checksum 42f822e91cc0d236b7273e986a859802
  Compiled from "GenericTest.java"
public class GenericTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#18         // java/lang/Object."<init>":()V
   #2 = Class              #19            // GenericBean
   #3 = Methodref          #2.#18         // GenericBean."<init>":()V
   #4 = Methodref          #7.#20         // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #5 = Methodref          #2.#21         // GenericBean.setValue:(Ljava/lang/Object;)V
   #6 = Methodref          #2.#22         // GenericBean.getValue:()Ljava/lang/Object;
   #7 = Class              #23            // java/lang/Integer
   #8 = Class              #24            // GenericTest
   #9 = Class              #25            // java/lang/Object
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               SourceFile
  #17 = Utf8               GenericTest.java
  #18 = NameAndType        #10:#11        // "<init>":()V
  #19 = Utf8               GenericBean
  #20 = NameAndType        #26:#27        // valueOf:(I)Ljava/lang/Integer;
  #21 = NameAndType        #28:#29        // setValue:(Ljava/lang/Object;)V
  #22 = NameAndType        #30:#31        // getValue:()Ljava/lang/Object;
  #23 = Utf8               java/lang/Integer
  #24 = Utf8               GenericTest
  #25 = Utf8               java/lang/Object
  #26 = Utf8               valueOf
  #27 = Utf8               (I)Ljava/lang/Integer;
  #28 = Utf8               setValue
  #29 = Utf8               (Ljava/lang/Object;)V
  #30 = Utf8               getValue
  #31 = Utf8               ()Ljava/lang/Object;
{
  public GenericTest();
    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 5: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #2                  // class GenericBean
         3: dup
         4: invokespecial #3                  // Method GenericBean."<init>":()V
         7: astore_1
         8: aload_1
         9: iconst_1
        10: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        13: invokevirtual #5                  // Method GenericBean.setValue:(Ljava/lang/Object;)V
        16: aload_1
        17: invokevirtual #6                  // Method GenericBean.getValue:()Ljava/lang/Object;
        20: checkcast     #7                  // class java/lang/Integer
        23: astore_2
        24: return
      LineNumberTable:
        line 7: 0
        line 8: 8
        line 10: 16
        line 11: 24
}
SourceFile: "GenericTest.java"

4.4 分析main方法

字節碼如下:

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #2                  // class GenericBean
         3: dup
         4: invokespecial #3                  // Method GenericBean."<init>":()V
         7: astore_1
         8: aload_1
         9: iconst_1
        10: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        13: invokevirtual #5                  // Method GenericBean.setValue:(Ljava/lang/Object;)V
        16: aload_1
        17: invokevirtual #6                  // Method GenericBean.getValue:()Ljava/lang/Object;
        20: checkcast     #7                  // class java/lang/Integer
        23: astore_2
        24: return
      LineNumberTable:
        line 7: 0
        line 8: 8
        line 10: 16
        line 11: 24
}

對比源代碼與字節碼,發現代碼:

Integer value = bean1.getValue();

對應的字節碼如下:

16: aload_1
17: invokevirtual #6                  // Method GenericBean.getValue:()Ljava/lang/Object;
20: checkcast     #7                  // class java/lang/Integer
23: astore_2

先調用了GenericBean.getValue()方法,然後將返回的Object類型的值強制轉換爲了Integer類型的值,這個過程是由編譯器完成的。

5.泛型繼承

5.1IntegerBean類的源代碼

public class IntegerBean extends GenericBean<Integer> {
    @Override
    public Integer getValue() {
       return super.getValue();
    }

    @Override
    public void setValue(Integer value) {
        super.setValue(value);
    }
}

5.2編譯IntegerBean類,使用下面命令

    javac IntegerBean.java

5.3查看IntegerBean.class字節碼,使用下面命令

    javap -v -c -s -p IntegerBean.class

字節碼如下:

 Classfile /home/user/test/java/IntegerBean.class
  Last modified Dec 7, 2018; size 613 bytes
  MD5 checksum 7b6cb615cd3e02f1421c171a22ee1837
  Compiled from "IntegerBean.java"
public class IntegerBean extends GenericBean<java.lang.Integer>
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #8.#23         // GenericBean."<init>":()V
   #2 = Methodref          #8.#24         // GenericBean.getValue:()Ljava/lang/Object;
   #3 = Class              #25            // java/lang/Integer
   #4 = Methodref          #8.#26         // GenericBean.setValue:(Ljava/lang/Object;)V
   #5 = Methodref          #7.#27         // IntegerBean.setValue:(Ljava/lang/Integer;)V
   #6 = Methodref          #7.#28         // IntegerBean.getValue:()Ljava/lang/Integer;
   #7 = Class              #29            // IntegerBean
   #8 = Class              #30            // GenericBean
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               getValue
  #14 = Utf8               ()Ljava/lang/Integer;
  #15 = Utf8               setValue
  #16 = Utf8               (Ljava/lang/Integer;)V
  #17 = Utf8               (Ljava/lang/Object;)V
  #18 = Utf8               ()Ljava/lang/Object;
  #19 = Utf8               Signature
  #20 = Utf8               LGenericBean<Ljava/lang/Integer;>;
  #21 = Utf8               SourceFile
  #22 = Utf8               IntegerBean.java
  #23 = NameAndType        #9:#10         // "<init>":()V
  #24 = NameAndType        #13:#18        // getValue:()Ljava/lang/Object;
  #25 = Utf8               java/lang/Integer
  #26 = NameAndType        #15:#17        // setValue:(Ljava/lang/Object;)V
  #27 = NameAndType        #15:#16        // setValue:(Ljava/lang/Integer;)V
  #28 = NameAndType        #13:#14        // getValue:()Ljava/lang/Integer;
  #29 = Utf8               IntegerBean
  #30 = Utf8               GenericBean
{
  public IntegerBean();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method GenericBean."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0

  public java.lang.Integer getValue();
    descriptor: ()Ljava/lang/Integer;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #2                  // Method GenericBean.getValue:()Ljava/lang/Object;
         4: checkcast     #3                  // class java/lang/Integer
         7: areturn
      LineNumberTable:
        line 8: 0

  public void setValue(java.lang.Integer);
    descriptor: (Ljava/lang/Integer;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: invokespecial #4                  // Method GenericBean.setValue:(Ljava/lang/Object;)V
         5: return
      LineNumberTable:
        line 13: 0
        line 14: 5

  public void setValue(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     #3                  // class java/lang/Integer
         5: invokevirtual #5                  // Method setValue:(Ljava/lang/Integer;)V
         8: return
      LineNumberTable:
        line 5: 0

  public java.lang.Object getValue();
    descriptor: ()Ljava/lang/Object;
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokevirtual #6                  // Method getValue:()Ljava/lang/Integer;
         4: areturn
      LineNumberTable:
        line 5: 0
}
Signature: #20                          // LGenericBean<Ljava/lang/Integer;>;
SourceFile: "IntegerBean.java"

5.4分析setValue方法

源代碼如下

@Override
public void setValue(Integer value) {
    super.setValue(value);
}

字節碼如下:

//第一個setValue方法
 public void setValue(java.lang.Integer);
    descriptor: (Ljava/lang/Integer;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: invokespecial #4                  // Method GenericBean.setValue:(Ljava/lang/Object;)V
         5: return
      LineNumberTable:
        line 13: 0
        line 14: 5
 //第二個setValue方法
  public void setValue(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     #3                  // class java/lang/Integer
         5: invokevirtual #5                  // Method setValue:(Ljava/lang/Integer;)V
         8: return
      LineNumberTable:
        line 5: 0

源代碼setValue對應的是字節碼中第一個setValue方法,那字節碼中第二個setValue方法怎麼來的呢?先看什麼是橋接方法?大概意思如下:
橋接方法是 JDK 1.5 引入泛型後,爲了使Java的泛型方法生成的字節碼和 1.5 版本前的字節碼相兼容,由編譯器自動生成的方法。可以通過Method.isBridge()方法來判斷一個方法是否是橋接方法,在字節碼中橋接方法會被標記爲ACC_BRIDGE和ACC_SYNTHETIC,其中ACC_BRIDGE用於說明這個方法是由編譯生成的橋接方法,ACC_SYNTHETIC說明這個方法是由編譯器生成,並且不會在源代碼中出現。

字節碼中第二個setValue方法就是橋接方法,是由編譯器自動生成。橋接方法調用了實際的泛型方法(從字節碼可以看出是調用第一個setValue的方法)。

猜想下面測試代碼如何:

public class GenericTest {
    public static void main(String[] args){
        GenericBean genericBean = new IntegerBean();
        genericBean.setValue(new Integer(1));
        genericBean.setValue(new Object());
    }
}

可以自己去運行看下會出什麼問題?下面屬於個人理解:
1.第二個setValue纔是真正重載的父類setValue。
2.第一個setValue方法@Override註解具有欺騙性質,它並不是重載方法。
3.上面程序main方法運行到第三行會崩潰,因爲第二個setValue會調用第一個setValue方法,導致Object對象不能夠強制轉換爲Integer對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章