【Java 泛型】使用上下边界通配符解决泛型擦除问题
前言
上一篇博客 【Java 泛型】泛型用法 ( 泛型编译期擦除 | 上界通配符 <? extends T> | 下界通配符 <? super T> ) 一、泛型擦除 章节中 , 讲到了泛型擦除问题 , 泛型只保留到了编译阶段 , 运行时就没有泛型的限制了 ;
本篇博客中介绍一种方法 , 使用上下边界通配符解决泛型擦除问题 ;
一、使用上边界通配符示例
接口类 :
public interface Data <T>{
void set(T t);
T get();
}
- 1
- 2
- 3
- 4
实现类 :
public class DataImpl<T extends Person> implements Data<T>{
private T t;
@Override
public void set(T t) {
}
@Override
public T get() {
return null;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
反编译查看 实现类的 字节码的信息 : 发现分别有 2 2 2 个 get 和 set 方法 ;
使用
javap -p DataImpl.class
- 1
命令 , 反编译 DataImpl.class 字节码文件 , 查看类中的主要方法 ;
D:\002_Project\004_Java_Learn\Main\out\production\Main>javap -p DataImpl.class
Compiled from "DataImpl.java"
public class DataImpl<T extends Person> implements Data<T> {
private T t;
public DataImpl();
public void set(T);
public T get();
public java.lang.Object get();
public void set(java.lang.Object);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
下面的 2 2 2 个方法 , 明显不符合 Java 语法规范 , 方法名和参数一样 ;
public T get();
public java.lang.Object get();
- 1
- 2
二、分析字节码的附加信息
下面分析字节码详细信息 ;
使用
javap -v DataImpl.class
- 1
命令 , 查看详细的字节码附加信息 ;
D:\002_Project\004_Java_Learn\Main\out\production\Main>javap -v DataImpl.class
Classfile /D:/002_Project/004_Java_Learn/Main/out/production/Main/DataImpl.class
Last modified 2021-9-7; size 907 bytes
MD5 checksum 90421d2a83f40d38de81c4c7f3cf341b
Compiled from "DataImpl.java"
public class DataImpl<T extends Person> extends java.lang.Object implements Data<T>
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#32 // java/lang/Object."<init>":()V
#2 = Methodref #5.#33 // DataImpl.get:()LPerson;
#3 = Class #34 // Person
#4 = Methodref #5.#35 // DataImpl.set:(LPerson;)V
#5 = Class #36 // DataImpl
#6 = Class #37 // java/lang/Object
#7 = Class #38 // Data
#8 = Utf8 t
#9 = Utf8 LPerson;
#10 = Utf8 Signature
#11 = Utf8 TT;
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 this
#18 = Utf8 LDataImpl;
#19 = Utf8 LocalVariableTypeTable
#20 = Utf8 LDataImpl<TT;>;
#21 = Utf8 set
#22 = Utf8 (LPerson;)V
#23 = Utf8 (TT;)V
#24 = Utf8 get
#25 = Utf8 ()LPerson;
#26 = Utf8 ()TT;
#27 = Utf8 ()Ljava/lang/Object;
#28 = Utf8 (Ljava/lang/Object;)V
#29 = Utf8 <T:LPerson;>Ljava/lang/Object;LData<TT;>;
#30 = Utf8 SourceFile
#31 = Utf8 DataImpl.java
#32 = NameAndType #12:#13 // "<init>":()V
#33 = NameAndType #24:#25 // get:()LPerson;
#34 = Utf8 Person
#35 = NameAndType #21:#22 // set:(LPerson;)V
#36 = Utf8 DataImpl
#37 = Utf8 java/lang/Object
#38 = Utf8 Data
{
public DataImpl();
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 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LDataImpl;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 5 0 this LDataImpl<TT;>;
public void set(T);
descriptor: (LPerson;)V
flags: ACC_PUBLIC
Code:
stack=0, locals=2, args_size=2
0: return
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this LDataImpl;
0 1 1 t LPerson;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 1 0 this LDataImpl<TT;>;
0 1 1 t TT;
Signature: #23 // (TT;)V
public T get();
descriptor: ()LPerson;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aconst_null
1: areturn
LineNumberTable:
line 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 this LDataImpl;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 2 0 this LDataImpl<TT;>;
Signature: #26 // ()TT;
public java.lang.Object get();
descriptor: ()Ljava/lang/Object;
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokevirtual #2 // Method get:()LPerson;
4: areturn
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LDataImpl;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 5 0 this LDataImpl<TT;>;
public void set(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 Person
5: invokevirtual #4 // Method set:(LPerson;)V
8: return
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this LDataImpl;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 9 0 this LDataImpl<TT;>;
}
Signature: #29 // <T:LPerson;>Ljava/lang/Object;LData<TT;>;
SourceFile: "DataImpl.java"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
主要分析 下面 2 2 2 个方法的详细字节码数据 ;
public void set(T);
public void set(java.lang.Object);
- 1
- 2
public void set(T)
方法的字节码详细数据如下 :
public void set(T);
descriptor: (LPerson;)V
flags: ACC_PUBLIC
Code:
stack=0, locals=2, args_size=2
0: return
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this LDataImpl;
0 1 1 t LPerson;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 1 0 this LDataImpl<TT;>;
0 1 1 t TT;
Signature: #23 // (TT;)V
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
public void set(java.lang.Object)
的字节码详细数据如下 : 该方法是桥接方法 ;
public void set(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 Person
5: invokevirtual #4 // Method set:(LPerson;)V
8: return
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this LDataImpl;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 9 0 this LDataImpl<TT;>;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
分析 public void set(java.lang.Object)
方法 :
该方法传入 Object 类型 , 所有的类都是 Object 子类 ;
descriptor: (Ljava/lang/Object;)V
说明该方法的参数是 Ljava/lang/Object;
类型 , 返回值是 void
类型 ;
ACC_BRIDGE
标识 标明 该该方法是一个桥接方法 ;
0: aload_0
从局部变量 0 装载引用类型值到操作数栈 ;
1: aload_1
从局部变量 1 装载引用类型值到操作数栈 ;
2: checkcast #3
检查该值是否是常量值 #3
的引用 , 也就是检查参数中传入的 Object 参数是否是 Person 类型 ;
Constant pool:
#3 = Class #34 // Person
- 1
- 2
5: invokevirtual #4
如果上一步检查 , 传入的参数是 Person 类型 , 就调用常量池中的 #4
常量对应的方法 , 也就是实际的 public void set(T)
方法 ;
Constant pool:
#4 = Methodref #5.#35 // DataImpl.set:(LPerson;)V
- 1
- 2
通过 上下边界 通配符 解决 泛型擦除问题 ;
文章来源: hanshuliang.blog.csdn.net,作者:韩曙亮,版权归原作者所有,如需转载,请联系作者。
原文链接:hanshuliang.blog.csdn.net/article/details/120161519
- 点赞
- 收藏
- 关注作者
评论(0)