浅谈Java泛型中的<? extends E>和<? super E>的区别

本文深入探讨了Java泛型中的上限通配符&lt;? extends E&gt;和下限通配符&lt;? super E&gt;的使用方法及原理,通过具体实例解释了PECS法则的应用,并介绍了它们在方法参数中的应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引入
再说这个之前,先来看一段代码:
假设有这么几个类及其继承关系,后面的例子也用这几个类作为基础示范
class People {
     //人
}
class Man extends People {
     //男人
}
class Woman extends People {
     //女人
}
class Boy extends Man {
     //男孩
}

可以看到,man是people的子类,那么:
 
   List<People> list= new ArrayList<Man>();
     
是否可以编译通过?


很明显,编译的时候报错了。man是people的子类,ArrayList是List的子类,但并不代表List<Man>是List<People>的子类。所以便有了 有限通配符
<? extends E>
<? extends E> 是 Upper Bound(上限) 的通配符,用来限制元素的类型的上限,比如:
List<? extends People> list_1 = null;
表示集合中的元素上限是People,即只能是People或People的子类,所以下面的赋值是合法的,编译时候不会报错:

但不能是其父类,否则编译时候就报错了:

接下来对其读写数据进行了解:
1、读
不管给该list_1如何赋值,可以保证的是里面存放的一定是People或People的子类,编译器可以确定获取的是People类型,所以可以直接从该集合中读取到People类型。即读取是允许的。

2、写

People、Man、Woman都是People类或其子类,但是这里却是编译错误的。原因是? extends People>仅仅告诉编译器该集合元素类型上限是People,这里编译器并不能确定具体的类型,即它可能实际指向了Man,但是你却add一个Woman类型,所以这里编译器不允许这么做。
<? super E>
<? super E> 是 Lower Bound(下限) 的通配符 ,用来限制元素的类型下限,比如:
List<? super Man> list_4 = null;
该表示给出了集合中元素的下限是Man,即只能为Man或者Man的父类,而不能是Man的子类,如下:

接下来对其读写数据进行了解:
1、读
允许从该集合获取元素,但是无法保证里面存放的是Man或者是Woman,唯一可以确定的是存放的是Object或其子类,而无法确定具体的类型。

这样都没错,但是实际用的时候还是要注意,像这样获取Woman可能导致异常。
2、写
可以确定的是集合中的元素一定是Man或Man的子类,所以添加Man或Boy都是正确的,但是不能添加非Man的子类:


使用场景
很多时候都是用它来当作方法中的形参。
这里先了解下PECS法则
1、PECS
PECS指“Producer Extends,Consumer Super”。换句话说,如果参数化类型表示一个生产者,就使用<? extends T>;如果它表示一个消费者,就使用<? super T>
2、例子
这里使用网上常见的例子水果来说明,有如下关系:

假设此时有个水果供应商Produce,
class Produce<E> {
     public void produce(List<E> list) {
           for (E e : list) {
                //生产...
                System.out.println("批量生产...");
           }
     }
}

它主要销售水果
Producer<Pear> p = new Produce<>();
List<Pear> pears = new ArrayList<Pear>();
p.produce(pears);
这样并没有什么问题。但是万一他突然想换成销售苹果了,此时:

这样就会发现,编译并不能通过,因为List<E>已经在初始化时确定为Pear了,而不再兼容Appler类型,即使你最开始使用的是Produce<Fruit>,即方法produce的参数list为List<Fruit>,虽然Apple和Pear是Fruit的子类,但是由上面的引入知识知道,List<Fruit>并不是List<Apple>的父类,即这样也是行不通的,所以这里就需要使用List<? extends E> list了。
修改后如下:
class Producer<E> {
     public void produce(List<? extends E> list) {
           for (E e : list) { //利用<? extends E>读取的特性
                //生产...
           }
         System.out.println("批量生产完成...");
     }
}

此时只要供应商new的时候为Fruit,则生产的货物只要为Fruit或其子类即可,所以Pear和Apple都可通过。如下:

接着举一个消费者的例子(可能例子举得不是很好)
//消费者
class Consumer<E> {
     public E consume(List<E> list) {
           E e = list.get(0); //模拟消费一个(感觉用队列比较合适)
           return e;
     }
}
每次消费者都从一个list中消费一个。加入有一个红苹果消费者:

这里是没什么问题的,但是红苹果也是苹果,如果这样呢:

这时候,<? super E>派上用场了。
//消费者
class Consumer<E> {
     public E consume(List<? super E> list) {
           E e = (E) list.get(0); //模拟消费一个(感觉用队列比较合适)
           return e;
     }
}
此时再按刚才的操作:

编译并不会出问题了。

其实,在java提供的许多类库里就有用到了,比如Collections的静态方法copy:

为了保证在list复制过程中类型的安全,限制了原list的上限,保证了目标数组的下限。

<think>好的,我现在需要回答用户关于Java中`<? extends T>``<? super T>`区别的问题。用户之前已经问过深拷贝浅拷贝,现在转向,可能是在学习Java的高级特性,需要理解这些通配符的用法区别。 首先,我应该回忆一下的基本概念。Java提供了编译时的类检查,避免类转换错误。通配符`?`用于表示未知类,而`extends``super`用来限定这个未知类的范围。 接下来,用户问的是`<? extends T>``<? super T>`的区别。我记得这涉及到PECS原则(Producer Extends, Consumer Super),但需要详细解释这两个通配符的作用适用场景。 对于`<? extends T>`,它表示类是T或其子类。这种通配符通常用于从集合中读取数据,即作为生产者。例如,一个`List<? extends Number>`可以接受`List<Integer>`或`List<Double>`。但是,不能向这样的列表中添加元素(除了null),因为编译器无法确定具体的类。 然后是`<? super T>`,它表示类是T或其父类。这种通配符适用于向集合中添加元素,即作为消费者。例如,`List<? super Integer>`可以接受`List<Integer>`、`List<Number>`甚至`List<Object>`。这时候,可以向列表中添加Integer类的元素,但读取时只能以Object类获取,因为具体类不确定。 需要举例子说明两者的使用场景,比如在方法参数中的应用。比如,一个复制方法,可能使用`<? extends T>`作为来源,`<? super T>`作为目标,这样来源是生产者,目标是消费者,符合PECS原则。 同时,要强调两者的界限:`extends`限制了上界,`super`限制了下界。这影响了类安全方法的允许操作,比如添加或读取的限制。 可能用户还容易混淆这两个符号的使用场合,需要明确何时使用哪一个。比如,当需要从数据结构获取数据时用extends,当需要存入数据时用super。 另外,需要注意类擦除的问题,虽然Java在运行时类信息会被擦除,但编译器会根据这些通配符进行类检查,确保代码的安全性。 最后,总结两者的主要区别,应用场景,以及可能带来的限制,比如类转换的问题或者添加元素的限制。确保用户能够理解何时选择哪一种通配符来达到他们的设计目的。</think>--- ### Java中`<? extends T>``<? super T>`的区别 --- #### **1. 核心概念** - **`<? extends T>`(上界通配符)** 表示**未知的具体类,但必须是`T`或其子类**。 **特点**: - 适用于从结构中**读取数据**(生产者角色)。 - **不能写入数据**(除了`null`)。 - **`<? super T>`(下界通配符)** 表示**未知的具体类,但必须是`T`或其父类**。 **特点**: - 适用于向结构中**写入数据**(消费者角色)。 - **可以读取数据**,但返回类只能是`Object`。 --- #### **2. 使用场景与示例** ##### **`<? extends T>`的典场景** **示例:读取集合中的元素** ```java // 定义一个方法,接受Number或其子类的集合 public static void printNumbers(List<? extends Number> list) { for (Number num : list) { System.out.println(num); // 安全读取 } } // 调用示例 List<Integer> intList = Arrays.asList(1, 2, 3); printNumbers(intList); // 合法:Integer是Number的子类 ``` **限制:不能写入** ```java list.add(42); // 编译错误!无法确定实际类 ``` --- ##### **`<? super T>`的典场景** **示例:向集合中添加元素** ```java // 定义一个方法,接受Integer或其父类的集合 public static void addIntegers(List<? super Integer> list) { list.add(42); // 安全写入 list.add(100); } // 调用示例 List<Number> numList = new ArrayList<>(); addIntegers(numList); // 合法:Number是Integer的父类 ``` **限制:读取时类不确定** ```java Object obj = list.get(0); // 只能以Object类接收 ``` --- #### **3. 关键区别** | **对比维度** | **`<? extends T>`** | **`<? super T>`** | |--------------------|-----------------------------------------------|--------------------------------------------| | **类范围** | `T`及其子类(上界) | `T`及其父类(下界) | | **数据操作** | 只读(生产者) | 可写(消费者) | | **读取返回值** | 明确的`T`类 | 只能是`Object`类 | | **写入限制** | 除`null`外不可写入 | 可写入`T`及其子类对象 | | **PECS原则** | Producer Extends(生产者用`extends`) | Consumer Super(消费者用`super`) | --- #### **4. PECS原则(Producer-Extends, Consumer-Super)** - **生产者(Producer)** 若结构**提供数据**(如集合的读取),使用`<? extends T>`。 ```java // 生产者:从src集合读取数据 public static <T> void copy(List<? extends T> src, List<? super T> dest) { for (T item : src) { dest.add(item); // src是生产者,dest是消费者 } } ``` - **消费者(Consumer)** 若结构**消费数据**(如集合的写入),使用`<? super T>`。 ```java // 消费者:向dest集合写入数据 List<Object> dest = new ArrayList<>(); copy(Arrays.asList("A", "B", "C"), dest); // String是Object的子类 ``` --- #### **5. 实际应用示例** - **`Collections.copy()`方法的实现** ```java public static <T> void copy(List<? super T> dest, List<? extends T> src) { // src是生产者(读取),dest是消费者(写入) for (int i = 0; i < src.size(); i++) { dest.set(i, src.get(i)); } } ``` - **工具类设计** ```java // 合并多个生产者集合 public static <T> List<T> merge(List<? extends T> list1, List<? extends T> list2) { List<T> result = new ArrayList<>(); result.addAll(list1); result.addAll(list2); return result; } ``` --- #### **6. 注意事项** 1. **类安全** - `<? extends T>`的读取是类安全的,但写入不明确。 - `<? super T>`的写入是类安全的,但读取需要强制类转换。 2. **灵活性与限制的权衡** - `extends`提供更严格的读取控制,`super`提供更灵活的写入能力。 3. **与方法结合** - 若需要同时支持读写,优先使用方法(如`<T> void process(List<T> list)`)。 --- #### **7. 总结** - **`<? extends T>`**:用于安全读取,限制写入。 - **`<? super T>`**:用于安全写入,限制读取类。 - **核心选择依据**: - 需要读取数据时,选择`extends`。 - 需要写入数据时,选择`super`。 - 遵循PECS原则,可显著提高代码的灵活性安全性。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值