文章目录
前言
一、使用上边界通配符示例
二、分析字节码的附加信息
前言
上一篇博客 【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 22 个 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 22 个方法 , 明显不符合 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 22 个方法的详细字节码数据 ;
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
通过 上下边界 通配符 解决 泛型擦除问题 ;