【Java 泛型】泛型用法 ( 泛型编译期擦除 | 上界通配符 <? extends T> | 下界通配符 <? super T> )

简介: 【Java 泛型】泛型用法 ( 泛型编译期擦除 | 上界通配符 <? extends T> | 下界通配符 <? super T> )

文章目录

一、泛型擦除

二、泛型的上界通配符 <? extends T>

三、泛型的下界通配符 <? super T>





一、泛型擦除


泛型只保留到 编译期 , 在 编译完毕后 , 泛型就不存在了 ;


在运行时 , 通过反射 , 调用泛型类 , 即使违反了泛型规则 , 也能进行相关操作 , 这是因为 在运行时 , 已经没有泛型相关的限制 , 泛型限制在编译时就已经被擦除了 ;


但是 泛型的信息 , 保存在了常量表中 , 仍然可以获取到 ;



泛型擦除 是为了 泛型可以兼容 老版本的 JDK 而设计的 ,


泛型是 JDK 5 加入的 , 如果添加了泛型 , 导致字节码的格式改变 , 必然导致之前版本的 JDK 无法运行有泛型的字节码程序 ;



给定一个 Demo.java 类 , 其中定义了一个 <T> T get() 方法 ;


public class Demo {
    public <T> T get(){
        T t = null;
        return t;
    }
}



将其编译后 , 查看字节码附加信息 ;


D:\java>javap -v Demo.class
Classfile /D:/java/Demo.class
  Last modified 2021-9-7; size 307 bytes
  MD5 checksum 727bc59421b23a5f0a31af0e91630ab8
  Compiled from "Demo.java"
public class Demo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#14         // java/lang/Object."<init>":()V
   #2 = Class              #15            // Demo
   #3 = Class              #16            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               get
   #9 = Utf8               ()Ljava/lang/Object;
  #10 = Utf8               Signature
  #11 = Utf8               <T:Ljava/lang/Object;>()TT;
  #12 = Utf8               SourceFile
  #13 = Utf8               Demo.java
  #14 = NameAndType        #4:#5          // "<init>":()V
  #15 = Utf8               Demo
  #16 = Utf8               java/lang/Object
{
  public Demo();
    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
  public <T extends java.lang.Object> T get();
    descriptor: ()Ljava/lang/Object;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=1
         0: aconst_null
         1: astore_1
         2: aload_1
         3: areturn
      LineNumberTable:
        line 3: 0
        line 4: 2
    Signature: #11                          // <T:Ljava/lang/Object;>()TT;
}
SourceFile: "Demo.java"


Demo 中的 get 方法类型返回值是 Ljava/lang/Object , 不是泛型 T , 这就是泛型在字节码中被擦除了 ;


descriptor: ()Ljava/lang/Object;



执行下面的代码 , 在运行时 , 使用反射 , 向 list1 集合中添加 int 类型的元素 , 添加成功 ; 说明在运行时 , 不再进行泛型检查 , 即使不符合泛型要求 , 也能操作成功 ;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class Main {
    public static void main(String[] args) {
        // 编译器 在 编译期 进行检查
        List<String> list1 = new ArrayList<>();
        //list1.add(1);
        // 编译器 在 编译期 不进行检查
        List list2 = new ArrayList<String>();
        //list2.add(1);
        try {
            Method method = ArrayList.class.getMethod("add", Object.class);
            method.invoke(list1, 1);
            System.out.println("list1 集合大小 : " + list1.size());
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}


执行结果 :



image.png





二、泛型的上界通配符 <? extends T>


泛型 上界通配符 : <T extends Person> 表示泛型 T 是 Person 的子类 , <? extends T> 泛型类型 只能是 T 的子类 ;


<T extends Person> 只能在声明泛型时使用 , 不能在 使用 泛型 时使用 ;
public class Data<T extends Person> {
}


<? extends T> 只能在使用 泛型 时使用 , 不能在声明 泛型类/方法 时 使用 ;
Data<? extends Person> data6 = new Data<>();



3 33 个有继承关系的类 :


public class Animal {
}


public class Person extends Animal {
}


public class Woman extends Person {
}



泛型类 : 该泛型类接收一个泛型 , 该泛型 T 是 Person 的子类 ;


public class Data<T extends Person> {
}



main 函数 :


   

// 上边界通配符
        // 符合要求 , 可设置 Woman 的父类 , Woman 以及 Person 类
        Data<? super Woman> data4 = new Data<>();
        // 符合要求 , 可设置 Person 类
        Data<? super Person> data5 = new Data<>();
        // 不符合要求 , Animal 是最顶层的类 , 其取值都不符合 <T extends Person> 要求
        Data<? super Animal> data6 = new Data<>();



报错信息 : 传入不符合要求的泛型的报错信息 ;




image.png




三、泛型的下界通配符 <? super T>


泛型 下界通配符 : <? super T> 泛型类型 只能是 T 的父类 ;


<? super T> 只能在使用 泛型 时使用 , 不能在声明 泛型类/方法 时 使用 ;
Data<? super Person> data6 = new Data<>();



3 33 个有继承关系的类 :


 
         


public class Person extends Animal {
}


public class Woman extends Person {
}



泛型类 : 该泛型类接收一个泛型 , 该泛型 T 是 Person 的子类 ;


public class Data<T extends Person> {
}



main 函数 :


   

// 上边界通配符
        // 符合要求 , 可设置 Woman 的父类 , Woman 以及 Person 类
        Data<? super Woman> data4 = new Data<>();
        // 符合要求 , 可设置 Person 类
        Data<? super Person> data5 = new Data<>();
        // 不符合要求 , Animal 是最顶层的类 , 其取值都不符合 <T extends Person> 要求
        //Data<? super Animal> data6 = new Data<>();


报错信息 : 不符合要求 , Animal 是最顶层的类 , 其取值都不符合 要求


image.png

目录
相关文章
|
3月前
|
安全 Java
Java之泛型使用教程
Java之泛型使用教程
304 10
|
5月前
|
安全 Java API
在Java中识别泛型信息
以上步骤和示例代码展示了怎样在Java中获取泛型类、泛型方法和泛型字段的类型参数信息。这些方法利用Java的反射API来绕过类型擦除的限制并访问运行时的类型信息。这对于在运行时进行类型安全的操作是很有帮助的,比如在创建类型安全的集合或者其他复杂数据结构时处理泛型。注意,过度使用反射可能会导致代码难以理解和维护,因此应该在确有必要时才使用反射来获取泛型信息。
236 11
|
8月前
|
安全 Java 开发者
Java 泛型中的通配符 T,E,K,V,?有去搞清楚吗?
本文介绍了Java泛型及其通配符的使用方法与作用。泛型是JDK5引入的特性,提供编译时类型安全检测,避免运行时类型转换异常。文中详细解析了常见通配符T、E、K、V、?的含义及应用场景,如无界通配符`&lt;?&gt;`、上界通配符`&lt;? extends T&gt;`和下界通配符`&lt;? super T&gt;`。通过代码示例,展示了泛型在类型安全、消除强制转换、代码复用和增强可读性等方面的优势。最后强调深入理解技术原理的重要性,以提升开发能力。
383 0
|
9月前
|
存储 Java 编译器
Java泛型类型擦除以及类型擦除带来的问题
本文主要讲解Java中的泛型擦除机制及其引发的问题与解决方法。泛型擦除是指编译期间,Java会将所有泛型信息替换为原始类型,并用限定类型替代类型变量。通过代码示例展示了泛型擦除后原始类型的保留、反射对泛型的破坏以及多态冲突等问题。同时分析了泛型类型不能是基本数据类型、静态方法中无法使用泛型参数等限制,并探讨了解决方案。这些内容对于理解Java泛型的工作原理和避免相关问题具有重要意义。
526 0
|
移动开发 前端开发 Java
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
11601 5
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
|
存储 Java 编译器
Java泛型类型擦除以及类型擦除带来的问题
泛型擦除是指Java编译器在编译期间会移除所有泛型信息,使所有泛型类型在运行时都变为原始类型。例如,`List&lt;String&gt;` 和 `List&lt;Integer&gt;` 在JVM中都视为 `List`。因此,通过 `getClass()` 比较两个不同泛型类型的 `ArrayList` 实例会返回 `true`。此外,通过反射调用 `add` 方法可以向 `ArrayList&lt;Integer&gt;` 中添加字符串,进一步证明了泛型信息在运行时被擦除。
265 2
|
存储 安全 Java
深入理解Java中的FutureTask:用法和原理
【10月更文挑战第28天】`FutureTask` 是 Java 中 `java.util.concurrent` 包下的一个类,实现了 `RunnableFuture` 接口,支持异步计算和结果获取。它可以作为 `Runnable` 被线程执行,同时通过 `Future` 接口获取计算结果。`FutureTask` 可以基于 `Callable` 或 `Runnable` 创建,常用于多线程环境中执行耗时任务,避免阻塞主线程。任务结果可通过 `get` 方法获取,支持阻塞和非阻塞方式。内部使用 AQS 实现同步机制,确保线程安全。
1291 3
|
IDE Java 编译器
Java:如何确定编译和运行时类路径是否一致
类路径(Classpath)是JVM用于查找类文件的路径列表,对编译和运行Java程序至关重要。编译时通过`javac -classpath`指定,运行时通过`java -classpath`指定。IDE如Eclipse和IntelliJ IDEA也提供界面管理类路径。确保编译和运行时类路径一致,特别是外部库和项目内部类的路径设置。
737 5
|
存储 安全 Java
🌱Java零基础 - 泛型详解
【10月更文挑战第7天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
164 1
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
290 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用