Java 泛型使用上下边界通配符解决泛型擦除问题
Posted 韩曙亮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 泛型使用上下边界通配符解决泛型擦除问题相关的知识,希望对你有一定的参考价值。
前言
上一篇博客 【Java 泛型】泛型用法 ( 泛型编译期擦除 | 上界通配符 <? extends T> | 下界通配符 <? super T> ) 一、泛型擦除 章节中 , 讲到了泛型擦除问题 , 泛型只保留到了编译阶段 , 运行时就没有泛型的限制了 ;
本篇博客中介绍一种方法 , 使用上下边界通配符解决泛型擦除问题 ;
一、使用上边界通配符示例
接口类 :
public interface Data <T>{
void set(T t);
T get();
}
实现类 :
public class DataImpl<T extends Person> implements Data<T>{
private T t;
@Override
public void set(T t) {
}
@Override
public T get() {
return null;
}
}
反编译查看 实现类的 字节码的信息 : 发现分别有 2 2 2 个 get 和 set 方法 ;
使用
javap -p DataImpl.class
命令 , 反编译 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);
}
下面的 2 2 2 个方法 , 明显不符合 Java 语法规范 , 方法名和参数一样 ;
public T get();
public java.lang.Object get();
二、分析字节码的附加信息
下面分析字节码详细信息 ;
使用
javap -v DataImpl.class
命令 , 查看详细的字节码附加信息 ;
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"
主要分析 下面 2 2 2 个方法的详细字节码数据 ;
public void set(T);
public void set(java.lang.Object);
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
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;>;
分析 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
5: invokevirtual #4
如果上一步检查 , 传入的参数是 Person 类型 , 就调用常量池中的 #4
常量对应的方法 , 也就是实际的 public void set(T)
方法 ;
Constant pool:
#4 = Methodref #5.#35 // DataImpl.set:(LPerson;)V
通过 上下边界 通配符 解决 泛型擦除问题 ;
以上是关于Java 泛型使用上下边界通配符解决泛型擦除问题的主要内容,如果未能解决你的问题,请参考以下文章
Java 泛型泛型用法 ( 泛型编译期擦除 | 上界通配符 <? extends T> | 下界通配符 <? super T> )