Java中List<? extends T>与List<? super T>的区别

2024-05-11 04:28
文章标签 java 区别 list super extends

本文主要是介绍Java中List<? extends T>与List<? super T>的区别,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 概述
  • List<? extends T>
  • List<? super T>
  • <T extends Comparable<? super T>>
  • 示例

概述

在Java中,List<? extends T>List<? super T>是用于表示具有泛型参数的列表的类型声明。它们之间的区别在于它们的作用和限制。

  1. List<? extends T>:这表示一个包含T或T的子类的列表。使用extends关键字限定了类型的上界。这种声明方式使得列表只能读取元素,而不能添加新的元素。原因是编译器无法确定要添加的元素类型是否与列表中的类型兼容。例如:
List<? extends Number> numbers = new ArrayList<>();
Number number = numbers.get(0); // 可以读取元素
numbers.add(10); // 编译错误,无法确定要添加的元素类型
  1. List<? super T>:这表示一个包含T或T的超类的列表。使用super关键字限定了类型的下界。这种声明方式使得列表可以添加T类型的元素,但读取元素时只能作为Object类型处理。例如:
List<? super Integer> integers = new ArrayList<>();
integers.add(10); // 可以添加Integer及其子类的元素
Object obj = integers.get(0); // 只能读取为Object类型
Integer integer = integers.get(0); // 编译错误,无法确定列表中的元素类型

总结:

  • List<? extends T>适用于读取元素,无法添加新元素。
  • List<? super T>适用于添加T类型的元素,读取时需要将元素视为Object类型。

这些通配符类型声明允许我们在类型安全的前提下操作具有不同泛型参数的列表。

List<? extends T>

当我们使用List<? extends T>时,无法添加新元素的原因涉及到泛型的协变性和类型安全性的考虑。

  1. 泛型协变性(Generics Covariance):

    • 泛型协变性是指对于类型AB,如果BA的子类型,那么List<B>List<A>的子类型。
    • 在Java中,数组是具有协变性的,即B[]A[]的子类型,但泛型是不具备协变性的。这是因为泛型在编译时会进行类型擦除,无法在运行时获得泛型参数的实际类型信息。
  2. 通配符限定的类型:

    • List<? extends T>表示一个未知的类型,该类型是TT的子类型。
    • 当我们声明一个类型为List<? extends T>的变量时,编译器无法确定具体的子类型是什么。它只知道该列表中的元素是TT的子类型,但无法具体指定是哪个子类型。
    • 这种情况下,编译器为了保持类型安全性,禁止我们向列表中添加新元素。因为无法确定要添加的元素与列表中元素的实际类型是否兼容。
  3. 类型不一致的问题:

    • 假设我们可以向List<? extends T>添加新元素,那么我们可能会遇到类型不一致的问题。
    • 如果编译器允许我们添加一个类型为U的元素到List<? extends T>中,而实际列表的元素类型是T的子类型V,那么在读取元素时,我们可能会将一个V类型的元素错误地赋值给类型为U的变量,导致类型错误。
    • 为了避免这种类型不一致的情况,编译器禁止在使用List<? extends T>时添加新元素,以保持类型安全性。

总结:

  • List<? extends T>表示一个未知的TT的子类型的列表。
  • 无法向List<? extends T>添加新元素,因为编译器无法确定列表的具体子类型,为了维持类型安全性,禁止添加操作。
  • 这样做是为了避免类型不一致的问题,即将错误类型的元素放入列表中,导致在读取元素时发生类型错误。
  • 泛型在编译时进行类型擦除,无法在运行时获得泛型参数的具体类型信息,这也是泛型不具备协变性的原因之一。

List<? super T>

当使用 List<? super T> 时,只能添加类型为 TT 的子类的元素,这涉及到泛型的逆变性和类型安全性的考虑。

  1. 泛型逆变性(Generics Contravariance):

    • 泛型逆变性是指对于类型 AB,如果 AB 的子类型,那么 List<B>List<A> 的子类型。
    • 在 Java 中,数组是具有逆变性的,即 B[]A[] 的子类型,但泛型是不具备逆变性的。这是因为泛型在编译时会进行类型擦除,无法在运行时获得泛型参数的实际类型信息。
  2. 通配符限定的类型:

    • List<? super T> 表示一个未知的类型,该类型是 TT 的超类。
    • 当我们声明一个类型为 List<? super T> 的变量时,编译器无法确定具体的超类类型是什么。它只知道该列表中的元素是 TT 的超类,但无法具体指定是哪个超类类型。
    • 这种情况下,编译器为了保持类型安全性,限制我们只能添加类型为 TT 的子类的元素。因为只有这样的元素才能确保与列表的类型约束兼容。
  3. 类型不一致的问题:

    • 假设我们可以向 List<? super T> 添加除 TT 的子类之外的元素,那么我们可能会遇到类型不一致的问题。
    • 如果编译器允许我们添加一个不是 TT 的子类的元素到 List<? super T> 中,而实际列表的超类类型是 T 的父类 U,那么在读取元素时,我们可能会将一个 U 类型的元素错误地赋值给类型为 T 的变量,导致类型错误。
    • 为了避免这种类型不一致的情况,编译器限制在使用 List<? super T> 时只能添加类型为 TT 的子类的元素,以保持类型安全性。

总结:

  • List<? super T> 表示一个未知的 TT 的超类的列表。
  • 只能向 List<? super T> 添加类型为 TT 的子类的元素,是为了保证类型安全性,避免类型不一致的问题。
  • 这样做提供了灵活性,允许引用超类类型为 TT 的超类的列表,并安全地添加符合约束的元素。
  • 泛型在编译时进行类型擦除,无法在运行时获得泛型参数的具体类型信息,这也是泛型不具备逆变性的原因之一。

<T extends Comparable<? super T>>

<T extends Comparable<? super T>> 是一个泛型约束(generic constraint),它用于限制泛型类型参数 T。
表示T必须是实现了Comparable接口,或者T的父类实现了Comparable接口

示例

  • 泛型类
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class Container<T> {private List<? super T> items = new ArrayList<>();public void addItem(T item) {items.add(item);}public void addItems(List<? extends T> itemList) {items.addAll(itemList);}public List<? super T> getItems() {return items;}
}Container<Animal> animalContainer = new Container<>();
animalContainer.addItem(new Dog());
animalContainer.addItem(new Cat());List<Dog> dogList = new ArrayList<>();
dogList.add(new Dog());
dogList.add(new Dog());animalContainer.addItems(dogList);List<? super Animal> items = animalContainer.getItems();

在这个例子中,Container 类是一个泛型类,使用 List<? super T> 作为成员变量的类型。我们可以向 Container 对象中添加 T 类型或 T 的子类的元素,并且可以通过 addItems 方法添加 List<? extends T> 类型的列表。通过 getItems 方法获取到的列表类型是 List<? super T>。

这些示例展示了 <? super T> 的应用场景,它可以用于方法参数、泛型类中的成员变量等,提供了更大的灵活性和多态性,允许处理不同类型的列表并进行元素操作。

  • 泛型方法
public static <T> void addElements(List<? super T> list, T[] elements) {for (T element : elements) {list.add(element);}
}List<Object> objectList = new ArrayList<>();
String[] strings = {"Hello", "World"};
addElements(objectList, strings);  // 向 Object 类型的列表添加 String 类型的元素

在这个例子中,addElements 是一个泛型方法,接受一个 List<? super T> 类型的列表和一个 T 类型的数组。我们可以将不同类型的数组元素添加到列表中,只要它们是 T 类型或 T 的子类。

  • 泛型接口
interface Eater<T> {void eat(List<? super T> foodList);
}class AnimalEater<T> implements Eater<T> {public void eat(List<? super T> foodList) {for (Object food : foodList) {// 进行吃食物的操作}}
}List<Object> foodList = new ArrayList<>();
foodList.add("Meat");
foodList.add("Fish");Eater<String> eater = new AnimalEater<>();
eater.eat(foodList);  // AnimalEater 实例可以接受 List<Object> 类型的食物列表

在这个例子中,Eater 是一个泛型接口,定义了一个 eat 方法,接受一个 List<? super T> 参数。AnimalEater 类实现了 Eater 接口,并针对不同类型的食物列表进行吃食物的操作。通过使用 <? super T>,我们可以在实现类中接受不同类型的食物列表,只要它们是 T 类型或 T 的超类。

这些示例展示了 <? super T> 在泛型方法和接口实现中的应用,它使得代码更加灵活,能够处理不同类型的数据并进行相应的操作。

这篇关于Java中List<? extends T>与List<? super T>的区别的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/978500

相关文章

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思

Java使用ANTLR4对Lua脚本语法校验详解

《Java使用ANTLR4对Lua脚本语法校验详解》ANTLR是一个强大的解析器生成器,用于读取、处理、执行或翻译结构化文本或二进制文件,下面就跟随小编一起看看Java如何使用ANTLR4对Lua脚本... 目录什么是ANTLR?第一个例子ANTLR4 的工作流程Lua脚本语法校验准备一个Lua Gramm

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

Java Optional的使用技巧与最佳实践

《JavaOptional的使用技巧与最佳实践》在Java中,Optional是用于优雅处理null的容器类,其核心目标是显式提醒开发者处理空值场景,避免NullPointerExce... 目录一、Optional 的核心用途二、使用技巧与最佳实践三、常见误区与反模式四、替代方案与扩展五、总结在 Java

基于Java实现回调监听工具类

《基于Java实现回调监听工具类》这篇文章主要为大家详细介绍了如何基于Java实现一个回调监听工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录监听接口类 Listenable实际用法打印结果首先,会用到 函数式接口 Consumer, 通过这个可以解耦回调方法,下面先写一个

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析

Java字符串处理全解析(String、StringBuilder与StringBuffer)

《Java字符串处理全解析(String、StringBuilder与StringBuffer)》:本文主要介绍Java字符串处理全解析(String、StringBuilder与StringBu... 目录Java字符串处理全解析:String、StringBuilder与StringBuffer一、St

springboot整合阿里云百炼DeepSeek实现sse流式打印的操作方法

《springboot整合阿里云百炼DeepSeek实现sse流式打印的操作方法》:本文主要介绍springboot整合阿里云百炼DeepSeek实现sse流式打印,本文给大家介绍的非常详细,对大... 目录1.开通阿里云百炼,获取到key2.新建SpringBoot项目3.工具类4.启动类5.测试类6.测

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

go 指针接收者和值接收者的区别小结

《go指针接收者和值接收者的区别小结》在Go语言中,值接收者和指针接收者是方法定义中的两种接收者类型,本文主要介绍了go指针接收者和值接收者的区别小结,文中通过示例代码介绍的非常详细,需要的朋友们下... 目录go 指针接收者和值接收者的区别易错点辨析go 指针接收者和值接收者的区别指针接收者和值接收者的