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

简介: 【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 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



通过 上下边界 通配符 解决 泛型擦除问题 ;


目录
相关文章
|
7月前
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
138 2
|
4月前
|
存储 Java 编译器
Java泛型类型擦除以及类型擦除带来的问题
泛型擦除是指Java编译器在编译期间会移除所有泛型信息,使所有泛型类型在运行时都变为原始类型。例如,`List&lt;String&gt;` 和 `List&lt;Integer&gt;` 在JVM中都视为 `List`。因此,通过 `getClass()` 比较两个不同泛型类型的 `ArrayList` 实例会返回 `true`。此外,通过反射调用 `add` 方法可以向 `ArrayList&lt;Integer&gt;` 中添加字符串,进一步证明了泛型信息在运行时被擦除。
97 2
|
5月前
|
Java API
[Java]泛型
本文详细介绍了Java泛型的相关概念和使用方法,包括类型判断、继承泛型类或实现泛型接口、泛型通配符、泛型方法、泛型上下边界、静态方法中使用泛型等内容。作者通过多个示例和测试代码,深入浅出地解释了泛型的原理和应用场景,帮助读者更好地理解和掌握Java泛型的使用技巧。文章还探讨了一些常见的疑惑和误区,如泛型擦除和基本数据类型数组的使用限制。最后,作者强调了泛型在实际开发中的重要性和应用价值。
131 0
[Java]泛型
|
6月前
|
Java 编译器 容器
Java——包装类和泛型
包装类是Java中一种特殊类,用于将基本数据类型(如 `int`、`double`、`char` 等)封装成对象。这样做可以利用对象的特性和方法。Java 提供了八种基本数据类型的包装类:`Integer` (`int`)、`Double` (`double`)、`Byte` (`byte`)、`Short` (`short`)、`Long` (`long`)、`Float` (`float`)、`Character` (`char`) 和 `Boolean` (`boolean`)。包装类可以通过 `valueOf()` 方法或自动装箱/拆箱机制创建。
72 9
Java——包装类和泛型
|
5月前
|
存储 安全 Java
🌱Java零基础 - 泛型详解
【10月更文挑战第7天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
46 1
|
5月前
|
Java 语音技术 容器
java数据结构泛型
java数据结构泛型
53 5
|
5月前
|
存储 Java 编译器
Java集合定义其泛型
Java集合定义其泛型
36 1
|
6月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
|
5月前
|
存储 Java 编译器
【用Java学习数据结构系列】初识泛型
【用Java学习数据结构系列】初识泛型
37 2
|
6月前
|
存储 安全 搜索推荐
Java中的泛型
【9月更文挑战第15天】在 Java 中,泛型是一种编译时类型检查机制,通过使用类型参数提升代码的安全性和重用性。其主要作用包括类型安全,避免运行时类型转换错误,以及代码重用,允许编写通用逻辑。泛型通过尖括号 `&lt;&gt;` 定义类型参数,并支持上界和下界限定,以及无界和有界通配符。使用泛型需注意类型擦除、无法创建泛型数组及基本数据类型的限制。泛型显著提高了代码的安全性和灵活性。