【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;@Overridepublic void set(T t) {}@Overridepublic T get() {return null;}
}
反编译查看 实现类的 字节码的信息 : 发现分别有 222 个 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);
}
下面的 222 个方法 , 明显不符合 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.classLast modified 2021-9-7; size 907 bytesMD5 checksum 90421d2a83f40d38de81c4c7f3cf341bCompiled from "DataImpl.java"
public class DataImpl<T extends Person> extends java.lang.Object implements Data<T>minor version: 0major version: 52flags: 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: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 1: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this LDataImpl;LocalVariableTypeTable:Start Length Slot Name Signature0 5 0 this LDataImpl<TT;>;public void set(T);descriptor: (LPerson;)Vflags: ACC_PUBLICCode:stack=0, locals=2, args_size=20: returnLineNumberTable:line 7: 0LocalVariableTable:Start Length Slot Name Signature0 1 0 this LDataImpl;0 1 1 t LPerson;LocalVariableTypeTable:Start Length Slot Name Signature0 1 0 this LDataImpl<TT;>;0 1 1 t TT;Signature: #23 // (TT;)Vpublic T get();descriptor: ()LPerson;flags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aconst_null1: areturnLineNumberTable:line 11: 0LocalVariableTable:Start Length Slot Name Signature0 2 0 this LDataImpl;LocalVariableTypeTable:Start Length Slot Name Signature0 2 0 this LDataImpl<TT;>;Signature: #26 // ()TT;public java.lang.Object get();descriptor: ()Ljava/lang/Object;flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETICCode:stack=1, locals=1, args_size=10: aload_01: invokevirtual #2 // Method get:()LPerson;4: areturnLineNumberTable:line 1: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this LDataImpl;LocalVariableTypeTable:Start Length Slot Name Signature0 5 0 this LDataImpl<TT;>;public void set(java.lang.Object);descriptor: (Ljava/lang/Object;)Vflags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETICCode:stack=2, locals=2, args_size=20: aload_01: aload_12: checkcast #3 // class Person5: invokevirtual #4 // Method set:(LPerson;)V8: returnLineNumberTable:line 1: 0LocalVariableTable:Start Length Slot Name Signature0 9 0 this LDataImpl;LocalVariableTypeTable:Start Length Slot Name Signature0 9 0 this LDataImpl<TT;>;
}
Signature: #29 // <T:LPerson;>Ljava/lang/Object;LData<TT;>;
SourceFile: "DataImpl.java"
主要分析 下面 222 个方法的详细字节码数据 ;
public void set(T);
public void set(java.lang.Object);
public void set(T)
方法的字节码详细数据如下 :
public void set(T);descriptor: (LPerson;)Vflags: ACC_PUBLICCode:stack=0, locals=2, args_size=20: returnLineNumberTable:line 7: 0LocalVariableTable:Start Length Slot Name Signature0 1 0 this LDataImpl;0 1 1 t LPerson;LocalVariableTypeTable:Start Length Slot Name Signature0 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;)Vflags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETICCode:stack=2, locals=2, args_size=20: aload_01: aload_12: checkcast #3 // class Person5: invokevirtual #4 // Method set:(LPerson;)V8: returnLineNumberTable:line 1: 0LocalVariableTable:Start Length Slot Name Signature0 9 0 this LDataImpl;LocalVariableTypeTable:Start Length Slot Name Signature0 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 泛型 父子_使用通配符和泛型:完成父子类关系的List对象的类型匹配
泛型和通配符 使用泛型和通配符都可以让一个方法所表示的算法逻辑适应多种类型. Java中具备继承关系的类A.B(A extends B)它们的集合List和List之间是没有继承关系的, 可以使用泛型 ...
- Java泛型总结---基本用法,类型限定,通配符,类型擦除
一.基本概念和用法 在Java语言处于还没有出现泛型的版本时,只能通过Object是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化.例如在哈希表的存取中,JDK1.5之前使用HashMap的 ...
- Java泛型解析(02):通配符限定
Java泛型解析(02):通配符限定 考虑一个这种场景.计算数组中的最大元素. [code01] public class ArrayUtil {public static <T> ...
- Java 泛型 super extends 边界
1 为什么要用通配符和边界? 使用泛型的过程中,经常出现一种很别扭的情况 比如我们有Fruit类,和它的派生类Apple class Fruit {} class Apple extends Frui ...
- Java泛型(1)--集合使用泛型Generic、自定义泛型、泛型在继承上的体现、通配符的使用
文章目录 泛型的概念 集合中使用泛型 自定义泛型结构 泛型在继承上的体现 通配符的使用 泛型的概念 集合容器类在设计阶段/声明阶段不能确定这个容器实际存的是什么类型的对象,所以在JDK1.5之前只能把 ...
- java泛型笔记2--上界通配符
擦除特性 虚拟机没有泛型类型对象.无论何时,定义一个泛型类型,都会自动提供一个相应的原始类型.这个原始类型的名字就是去掉类型参数后的泛型类型名.类型变量会被擦除,并替换成其限定类型,对于无限定的变量, ...
- java:泛型(自定义泛型类、自定义泛型接口、泛型的继承和通配符说明)
目录 一.泛型的介绍 二.泛型的语法 2.1 泛型的声明 2.2 泛型的实例化 2.3 泛型使用举例 2.3 泛型使用的注意事项和细节 2.4 泛型课堂练习题 2.5 自定义泛型类 2.6 自定义泛型 ...
- 【Java 泛型】泛型用法 ( 泛型类用法 | 泛型方法用法 | 泛型通配符 ? | 泛型安全检查 )
文章目录 一.泛型类用法 二.泛型方法用法 三.泛型通配符 <?> 四.泛型安全检查 五.完整代码示例 1.泛型类 / 方法 2.main 函数 一.泛型类用法 泛型类用法 : 使用时先声 ...
- java泛型方法 通配符_Java泛型教程–示例类,接口,方法,通配符等
java泛型方法 通配符 泛型是Java编程的核心功能之一,它是Java 5中引入的.如果您使用的是Java Collections ,并且版本5或更高版本,则可以肯定使用了它. 将泛型与集合类一起使 ...
最新文章
- AI复原上海民国名媛影像!1929年的小姐姐,时装精致不输现在
- 简单的Linux扫描仪应用:C语言实现
- Bootstrap 表单控件一(单行输入框input,下拉选择框select ,文本域textarea)
- C语言解释器的实现--存储结构(一)
- 梅朵是藏语鲜花的意思
- IT运维管理与ITIL
- 让使用SQLite的.NET应用自适应32位/64位系统
- 硬盘常规测试软件解析
- 【华为云技术分享】【昇腾】ModelArts与Atlas 200 DK云端协同开发——行人检测Demo(提供完整Demo)
- 在NHibernate的单表继承模式下通过父类Repository查询子类
- [jQuery] 速记一の屏蔽鼠标右键监听鼠标Event
- python sleep 毫秒_如何在python中休眠Webdriver毫秒
- 用Matlab处理信号从入门到入土1
- 中国石油大学《大学语文》第一阶段在线作业
- 《数据库原理与应用》课程实验报告三 --数据库的嵌套查询
- 先睹为快,Go2 Error 的挣扎之路
- 35行代码搞定事件研究法(下)
- 运维快速入门必备的 Linux 服务器安全简明指南,速收!
- 图形界面 II: 设置库的事件处理函数 (第三章)
- 网络基础之网络协议篇