java 泛型中? extends Class和? super Class的意义和PECS原则

本文深入探讨Java中泛型的PECS原则,解释为何不能向List<? extends A>添加B类型对象,以及如何利用泛型实现生产者和消费者模式。

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

最近在读Thinking in Java 的 Generics 章节,主要讲Java中泛型的设计理念和使用方法。

其中,关于通配符和关键字 ? 、extends、super 在泛型中的应用一直没有理清楚头绪,通过Google发现了一个有意思的问题觉得很值得记录一下,所以写下本文。

这个问题是这样的:对于class A{}class B extends A{}在如下代码中会出现编译错误:

        List<? extends A> list = new ArrayList<>();
        list.add(new B());/* exlipse error:The method add(capture#1-of ? extends A) in the type List<capture#1-of ? extends A> is not applicable for the arguments (B)*/

这个问题很奇怪,向一个List<? extends A>添加一个继承了A的对象B为什么不行?

其实虽然这句话从人的角度上理解是没毛病的,但是对Java来说,List中存放的是什么它在程序运行前完全不知道,他知道的仅仅是List中的东西可以被向上转型为A。

假如,你在运行的时候这个List中存放的是A的另一个子类class C extends A{}(不是B),对于存储C的List是不可以存储B的。

编译器是不允许你的代码在编译的时候出现这种情况的,也就是说,这个List只可以从里面取出数据(A接口)而不可以放里面放入数据。与之相对的是List<? super B>则是一个只可向里面放入数据(B的父类或本身)而不可以取出数据的List。

那么我们现在拥有了一个只读的容器和一个只写的容器,很容易想到,生产者和消费者的实现就通过这两个泛型。

我们通常称之为PECS原则:生产者(Producer)使用extends,消费者(Consumer)使用super。

以下是JDK 8 Collections.copy() source code:

/**
     * Copies all of the elements from one list into another.  After the
     * operation, the index of each copied element in the destination list
     * will be identical to its index in the source list.  The destination
     * list must be at least as long as the source list.  If it is longer, the
     * remaining elements in the destination list are unaffected. <p>
     *
     * This method runs in linear time.
     *
     * @param  <T> the class of the objects in the lists
     * @param  dest The destination list.
     * @param  src The source list.
     * @throws IndexOutOfBoundsException if the destination list is too small
     *         to contain the entire source List.
     * @throws UnsupportedOperationException if the destination list's
     *         list-iterator does not support the <tt>set</tt> operation.
     */
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        int srcSize = src.size();
        if (srcSize > dest.size())
            throw new IndexOutOfBoundsException("Source does not fit in dest");

        if (srcSize < COPY_THRESHOLD ||
            (src instanceof RandomAccess && dest instanceof RandomAccess)) {
            for (int i=0; i<srcSize; i++)
                dest.set(i, src.get(i));
        } else {
            ListIterator<? super T> di=dest.listIterator();
            ListIterator<? extends T> si=src.listIterator();
            for (int i=0; i<srcSize; i++) {
                di.next();
                di.set(si.next());
            }
        }
    }

Reference

<think>嗯,用户现在问的是Java的上限下限,也就是的通配符中的extendssuper。之前用户问过printf的格式化输出,现在转向了的基础知识。可能用户正在学习Java的基础,特别是这一块,想深入理解类参数的限制。 首先,我需要确定用户已经了解的基本概念,比如什么是类、方法,以及为什么需要。可能用户已经知道用于类安全,避免强制类转换,但上限下限可能对他们来说有点抽象。需要从基础讲起,但也要深入例子,帮助理解。 接下来,用户可能对上下限的应用场景不太清楚,比如什么时候用extends,什么时候用super。这时候需要举一些实际的例子,比如在集合类中的应用,比如List<? extends Number>List<? super Integer>的区别。同时,PECS原则(Producer Extends, Consumer Super)可能需要解释清楚,这样用户能理解在方法参数中如何选择。 另外,用户可能会混淆类参数的上限通配符的上限。比如,在定义类时,<T extends Number>方法的参数List<? extends Number>是不同的。需要指出这两者的区别,避免混淆。 可能用户在实际编程中遇到了需要限制的情况,比如写一个方法,只能处理数字类的列表,或者需要向列表中添加元素。这时候上限下限就显得很重要了。通过实际的代码示例,比如遍历一个extends的列表,或者向一个super的列表添加元素,能帮助用户更好地理解。 还要注意用户可能的误区,比如使用extends时不能添加元素,只能读取,而super可以添加特定类的元素。这些限制需要明确说明,避免用户在实际使用中出现错误。 另外,用户可能想知道上下限在类擦除后的影响,但这个问题比较复杂,可能暂时不需要深入,除非用户有更高级的问题。目前重点放在语法应用上。 最后,总结部分需要简明扼要,回顾上限下限的作用,以及它们的典应用场景,帮助用户巩固记忆。可能还需要提到常见的错误,比如错误地尝试添加元素到extends通配符的集合中,导致编译错误,提醒用户注意。 总之,回答需要结构清晰,从定义、语法、示例、应用场景到注意事项,逐步展开,确保用户能够逐步理解上下限的概念用法。同时,结合之前的printf问题,保持回答的一致性,使用用户已经适应的格式示例风格。</think>以下是关于Java**上限(Upper Bound)**与**下限(Lower Bound)**的详细解析: --- ### **一、核心概念** #### **1. 上限(`<? extends T>`)** - **作用**:限制的**最大范围**,表示必须是`T`或其**子类**。 - **本质**:定义类的**生产者(Producer)**,主要用于**安全读取数据**。 - **语法**:`Class<? extends T>` 或 `T<? extends U>` #### **2. 下限(`<? super T>`)** - **作用**:限制的**最小范围**,表示必须是`T`或其**父类**。 - **本质**:定义类的**消费者(Consumer)**,主要用于**安全写入数据**。 - **语法**:`Class<? super T>` 或 `T<? super U>` --- ### **二、代码示例与对比** #### **1. 上限(`extends`)** ```java // 定义方法:接收Number及其子类的列表(如Integer、Double) public static void printNumbers(List<? extends Number> list) { for (Number num : list) { // 安全读取 System.out.print(num + " "); } } // 调用 List<Integer> intList = Arrays.asList(1, 2, 3); List<Double> doubleList = Arrays.asList(1.1, 2.2); printNumbers(intList); // 合法 ✅ printNumbers(doubleList); // 合法 ✅ ``` **限制**: - **不可写入**:`list.add(10)` 会编译报错 ❌ - **原因**:Java无法确定实际类是否匹配(可能是`Integer`或`Double`) --- #### **2. 下限(`super`)** ```java // 定义方法:接收Integer及其父类的列表(如Number、Object) public static void addIntegers(List<? super Integer> list) { list.add(10); // 安全写入 ✅ list.add(20); } // 调用 List<Number> numList = new ArrayList<>(); List<Object> objList = new ArrayList<>(); addIntegers(numList); // 合法 ✅ addIntegers(objList); // 合法 ✅ ``` **限制**: - **读取不安全**:`Integer i = list.get(0)` 需强制类转换 ❌ - **原因**:实际类可能是`Number`或`Object` --- ### **三、核心规则(PECS原则)** | **场景** | **选择** | **记忆口诀** | |-----------------------|------------------|---------------------------| | 需要**读取数据**(生产者) | `<? extends T>` | **Producer Extends** | | 需要**写入数据**(消费者) | `<? super T>` | **Consumer Super** | --- ### **四、应用场景** #### **1. 上限的典用途** - **集合的只读操作** ```java // 安全遍历任何Number子类的集合 public static double sum(List<? extends Number> list) { double total = 0; for (Number num : list) { total += num.doubleValue(); } return total; } ``` - **API设计中的类约束** ```java // 限制键类必须实现Comparable class SortedMap<K extends Comparable<? super K>, V> { ... } ``` --- #### **2. 下限的典用途** - **写入集合** ```java // 将多个Integer添加到父类集合 public static void fill(List<? super Integer> list, int count) { for (int i = 0; i < count; i++) { list.add(i); // 安全写入 } } ``` - **回调函数设计** ```java // 接收Consumer<? super T>,兼容父类处理器 public void process(List<T> data, Consumer<? super T> consumer) { data.forEach(consumer); } ``` --- ### **五、类参数与通配符的对比** | **特性** | **类参数(`<T>`)** | **通配符(`<?>`)** | |---------------------|--------------------------|----------------------------| | 定义位置 | 类/方法声明时 | 方法参数或变量类 | | 可操作性 | 可直接使用类`T` | 需配合`extends`/`super` | | 典用途 | 定义类或方法 | 灵活约束已有 | | 示例 | `class Box<T> { ... }` | `void print(List<?> list)` | --- ### **六、常见误区与避坑指南** 1. **混淆`extends`与`super`** - 错误:尝试向`List<? extends Number>`添加`Double` - 正确:改用`List<? super Double>` 2. **过度使用通配符** - 错误:`Map<?, ?> map = new HashMap<>();` - 正确:明确具体类参数(除非确实需要完全未知类) 3. **忽略PECS原则** - 错误:在需要写入数据的方法中使用`<? extends T>` - 正确:根据操作方向选择通配符类 --- ### **七、底层原理** - **类擦除**:信息在编译后被擦除,转换为原始类(Raw Type) - **桥方法生成**:编译器自动生成方法保证类安全 - **通配符捕获**:通过辅助方法捕获具体类(如`helper(List<T> list)`) --- ### **总结** | **特性** | **上限(`<? extends T>`)** | **下限(`<? super T>`)** | |-----------------|------------------------------------|----------------------------------| | **方向** | 限制类为`T`的子类 | 限制类为`T`的父类 | | **操作权限** | 只读(安全读取) | 可写(安全写入) | | **典场景** | 数据生产者(如遍历集合) | 数据消费者(如填充集合) | 掌握上下限是写出**类安全**且**灵活**的Java代码的关键,尤其在集合操作API设计中应用广
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值