大数跨境
0
0

Java 泛型:彻底掌控 <? super T> 和 <? extends T>

Java 泛型:彻底掌控 <? super T> 和 <? extends T> Spring全家桶实战案例
2025-10-24
2
导读:Java 泛型:extends & super 关键字的使用
Spring Boot 3实战案例锦集PDF电子书已更新至130篇!

🎉🎉《Spring Boot实战案例合集》目前已更新185个案例,我们将持续不断的更新。文末有电子书目录。

💪💪永久更新承诺

我们郑重承诺,所有订阅合集的粉丝都将享受永久免费的后续更新服务

💌💌如何获取
订阅我们的合集点我订阅,并通过私信联系我们,我们将第一时间将电子书发送给您。

→ 现在就订阅合集

环境:Java21



1. 简介

在 Java 泛型中,<? super T> 叫下界通配符,表示参数类型得是 T 或其父类。用它能更灵活接收不同层级父类对象,常用于“写”场景,像往集合添加元素。<? extends T> 是上界通配符,意味着参数类型是 T 或其子类。它适合“读”操作,能从集合获取元素并安全转为 T 类型。二者各有适用场景,合理运用能提升代码灵活性与类型安全性,让泛型在处理不同类型数据时发挥强大威力,助力高效编程。

2.实战案例

2.1 为什么需要通配符和边界?

在使用泛型时,我们常常会陷入一些棘手的困境。

让我们通过一个实际的例子来说明。假设我们有一个 Animal 类,以及它的两个子类 Dog 和 Cat

public class Animal {}public class Dog extends Animal {}public class Cat extends Animal {}

接着,我们有一个简单的 Cage(笼子)类。这个笼子可以存放一个泛型的“动物”。

我们可以对这个笼子里的动物执行最基本的“放入”(放动物进笼子)和“取出”(从笼子拿出动物)操作,也就是 put() 和 get() 方法。

public class Cage<T> {  private T item;  public Cage(T t) {    item = t;  }  public void set(T t) {    item = t;  }  public T get() {    return item;  }}

接下来,我定义一个“动物笼子”。从逻辑上讲,动物笼子自然应该能够放狗(这里以狗作为动物的一种具体实例来举例)。

Cage<Animal> cage = new Cage<Dog>(new Dog()) ;

首先,编译都无法通过:

意思是:放狗的笼子不能转换为放动物的笼子。

是不是很无语!

  • 狗是一种动物

  • 但狗笼子不是动物笼子的一种

因此,即使包含的类型相互继承,容器本身也不会继承。因此,对 Cage<Dog> 的引用不能分配给 Cage<Animal>。

为了使泛型更符合人体工程学,Sun 的架构师设计了 <? extends T> 和 <? super T> 来关联。

2.2 什么是上界?

以下代码展示了上界通配符的用法:

Cage<? extends Animal>

一个能放任何种动物的笼子。

Cage<? extends Animal> 和 Cage<Dog> 之间的关键区别在于:Cage<? extends Animal> 是 Cage<Animal> 和 Cage<Dog> 两者的父类。

直接的好处就是:现在我们可以将一个“狗笼子”赋值给一个“动物笼子”变量了。

Cage<? extends Animal> p = new Cage<Dog>(new Dog());

接下来,我们再通过如下示例来进行扩展:我们定义Food食物类,然后在分为肉类(Meat)和水果类(Fruit)。Fruit 包括 Apple(苹果)和 Banana(香蕉)。Meat 包括 Pork(猪肉)和 Beef(牛肉)。Apple 有两种类型:GreenApple(青苹果)和 RedApple(红苹果)。

public class Food {}
public class Fruit extends Food {}public class Meat extends Food {}
public class Apple extends Fruit {}public class Banana extends Fruit {}public class Pork extends Meat {}public class Beef extends Meat {}
public class RedApple extends Apple {}public class GreenApple extends Apple {}
// 定义容器类public class Plate<T> {  private T item ;}

如下图所示,上界通配符 Plate<? extends Meat> 覆盖了图中的红色区域。

2.3 什么是下界?

相应地,"下限通配符 "也是如此:

Plate<? super Meat>

它表达的是相反的概念:一个能放 Meat 以及 Meat 任何父类的盘子。

Plate<? super Meat> 是 Plate<Meat> 的父类,但不是 Plate<Beef> 的父类。

对应前面的例子,Plate<? super Meat> 涵盖了图中红色区域所表示的范围。

2.4 上下界通配符的副作用

边界使不同 Java 泛型之间的转换变得更容易。不过,不要忘记,这种转换也会产生某些副作用。具体来说,容器的某些功能可能会丧失。

还是以 Plate 为例,我们可以对 Plate 做两件事:向 Plate 中 set() 新元素和从 Plate 中 get() 元素。

public class Plate<T> {  private T item;  public Plate(T t) { item = t; }  public void set(T t) { item = t; }  public T get() { return item; }}

上界通配符 <? extends T> 的副作用

如果使用 Plate<? extends Meat>(能放 Meat 及其子类),set() 方法会受限:

Plate<? extends Meat> plate = new Plate<Beef>(new Beef());// ❌ 编译错误!不能放入任何具体类型(包括 Beef)plate.set(new Beef()); // ✅ 可以取出,类型是 MeatMeat meat = plate.get(); 

原因:

编译器无法确定 plate 具体是 Plate<Beef>、Plate<Pork> 还是其他 Plate<? extends Meat>,所以禁止放入任何非 null 的具体类型(避免类型污染)。

但可以安全地取出,因为取出的类型一定是 Meat 或其子类(可赋值给 Meat)。

下界通配符 <? super T> 的副作用

如果使用 Plate<? super Meat>(能放 Meat 及其父类),get() 方法会受限:

Plate<? super Meat> plate = new Plate<Object>(new Object());// ✅ 可以放入 Beef(因为 Beef 是 Meat 的子类)plate.set(new Beef());// ❌ 编译错误!取出的类型只能是 ObjectMeat meat = plate.get();// ✅ 可以取出,但类型是 ObjectObject obj = plate.get();

原因:

编译器只能确定 plate 至少能盛放 Meat(可能是 Plate<Meat>、Plate<Object> 等),所以可以安全地放入 Meat 及其子类(如 Beef)。

但取出时只能用 Object 接收,因为具体类型可能是 Meat 或 Object(编译器无法保证更具体的类型)。

2.5 PECS原则

PECS 原则(Producer-Extends, Consumer-Super)

为了更好地记住 <? extends T> 和 <? super T> 的使用场景,有一个有用的记忆口诀:PECS。

  • Producer-Extends:如果你是一个生产者(从容器中获取数据),使用 <? extends T>

  • Consumer-Super:如果你是一个消费者(向容器中添加数据),使用 <? super T>

最后,做一个总结:

  • <? extends T> 用于“生产者”场景,允许你从集合中读取 T 或其子类的元素,但不能向集合中添加元素(除了 null)

  • <? super T> 用于“消费者”场景,允许你向集合中添加 T 或其子类的元素,但从集合中读取元素时只能赋值给 Object 类型

  • PECS 原则(Producer-Extends, Consumer-Super)可以帮助你快速确定在特定情况下应该使用哪种通配符。




以上是本篇文章的全部内容,如对你有帮助帮忙点赞+转发+收藏

推荐文章

Spring Boot 这个接口非常强大

太燃了!Spring Boot 七种拦截技术,横扫各种问题

高级开发!扩展@Async功能,动态管理异步任务

手写Spring MVC核心组件,底层原理如此简单

性能优化!3种方案优化Controller接口调用,性能提升N倍

神器!基于 Spring Boot 处理 CSV 超强工具,太方便了

性能优化!7个策略,让Spring Boot 处理每秒百万请求

自己动手实现Agent统计API接口调用耗时

太强了!Spring Boot 五大内置 "神兵" 工具

强大!借助Spring Boot内置动态刷新功能,实时更新组件

性能提升!@Async与CompletableFuture优雅应用

图片
图片
图片
图片
图片
图片
图片
图片
图片

【声明】内容源于网络
0
0
Spring全家桶实战案例
Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
内容 832
粉丝 0
Spring全家桶实战案例 Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
总阅读285
粉丝0
内容832