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對象。