【Java 泛型】使用上下边界通配符解决泛型擦除问题

举报
韩曙亮 发表于 2022/01/14 00:01:12 2022/01/14
【摘要】 文章目录 前言一、使用上边界通配符示例二、分析字节码的附加信息 前言 上一篇博客 【Java 泛型】泛型用法 ( 泛型编译期擦除 | 上界通配符 <? extends T> | 下界通配符...

前言

上一篇博客 【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

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。