<? extends T>是什么,<? super T>又是什么,PECS是指什么

一、<? extends T>是什么,<? super T>又是什么

在Java泛型中,<? extends T> 和 <? super T> 是通配符(Wildcard)的核心语法,用于类型边界约束。它们分别对应PECS法则中的生产者(Producer)消费者(Consumer)场景,具体含义、用法及限制如下:

1. <? extends T>(上界通配符)

含义
  • 表示集合的元素类型是TT任意子类(如TNumber,则可以是IntegerDouble等)。
  • 用于生产者场景(只读不写,集合是生产者),即从集合中安全读取数据。
用法
  • 方法参数:当方法需要读取集合元素时,使用List<? extends T>接收参数。
  • 返回值:当方法返回不确定具体子类的集合时,可用? extends T声明返回类型(但较少见)。
限制
  • 禁止写入非null元素:因为编译器无法确定具体类型,无法保证添加的元素类型安全。
    List<? extends Number> numbers = new ArrayList<Integer>();
    numbers.add(1); // 编译错误:无法确定具体类型
    numbers.add(null); // 允许,因为null是所有类型的子类
     
    
  • 读取时类型安全:读取的元素可赋值给TT的父类(如Object)。
代码示例
// 生产者方法:读取任意Number子类的List
public static void printNumbers(List<? extends Number> numbers) {
    for (Number num : numbers) {
        System.out.println(num); // 安全读取,类型为Number或其子类
    }
}

// 调用示例
List<Integer> integers = Arrays.asList(1, 2, 3);
printNumbers(integers); // 编译通过,Integer是Number的子类

2. <? super T>(下界通配符)

含义
  • 表示集合的元素类型是TT任意父类(如TInteger,则可以是NumberObject等)。
  • 用于消费者场景(只写不读,要写的类型是确定的),即向集合中安全添加数据。
用法
  • 方法参数:当方法需要写入集合元素时,使用List<? super T>接收参数。
  • 返回值:几乎不用于返回类型(因为类型信息丢失)。
限制
  • 读取时类型丢失:从集合中读取的元素只能用Object接收(无法确定具体类型)。
    List<? super Number> numbers = new ArrayList<Object>();
    Object obj = numbers.get(0); // 只能用Object接收
    Number num = numbers.get(0); // 编译错误:类型不安全

    写入时类型安全:可安全添加TT的子类元素。

代码示例
// 消费者方法:向任意Number父类的List中添加数据
public static void addNumbers(List<? super Number> numbers) {
    numbers.add(1);       // 安全添加Integer(Number的子类)
    numbers.add(1.1);     // 安全添加Double(Number的子类)
}

// 调用示例
List<Object> objects = new ArrayList<>();
addNumbers(objects); // 编译通过,Object是Number的父类

3. 对比总结

特性? extends T(上界)? super T(下界)
适用场景生产者(只读)消费者(只写)
类型约束元素为T或其子类元素为T或其父类
允许操作安全读取(T或父类)安全写入(T或子类)
禁止操作写入非null元素(类型不确定)读取具体类型(只能用Object
典型用途方法参数(如Collections.copy源)方法参数(如Collections.copy目标)

4. 实际应用场景

  • 集合拷贝:JDK中的Collections.copy(List<? super T> dest, List<? extends T> src)方法,确保源集合(生产者)可读,目标集合(消费者)可写。
  • 自定义工具类:如自定义栈的popAll(Collection<? super T> dest)方法,将栈元素弹出到任意父类集合中。
  • 框架设计:在需要灵活处理子类或父类集合的场景中,避免类型强制转换。

5. 注意事项

  • 避免在类定义中使用通配符:类定义应使用具体类型参数(如class Box<T>),通配符主要用于方法参数。
  • 优先使用通配符而非具体类型:在方法参数中,使用List<? extends T>List<T>更灵活(可接受子类集合)。
  • 类型擦除的影响:运行时通配符会被擦除为Object,但编译期的类型检查仍能保证安全。

通过理解? extends T? super T的差异及约束,可以编写更灵活、安全的泛型代码,避免运行时类型错误。

二、PECS法则

PECS法则(Producer Extends, Consumer Super)是Java泛型中用于确定通配符边界的核心原则,其核心逻辑是通过类型安全约束优化生产者(只读)消费者(只写)场景的代码设计。

以下从原理、代码示例、应用场景三个层面进行详细说明(略显重复,加深记忆~~~):

PECS法则原理

  • Producer Extends(生产者使用? extends T
    • 适用场景:当集合作为数据生产者(只读不写),需从集合中安全读取数据时。
    • 类型约束:通配符上限为T,即集合元素类型为T或其子类。
    • 限制:无法向集合添加非null元素(编译器无法确定具体类型)。
    • 优势:确保读取的数据一定是T或其子类,避免类型污染。
  • Consumer Super(消费者使用? super T
    • 适用场景:当集合作为数据消费者(只写不读),需向集合中安全添加数据时。
    • 类型约束:通配符下限为T,即集合元素类型为T或其父类。
    • 限制:从集合中读取的数据只能用Object接收(类型信息丢失)。
    • 优势:确保添加的数据一定是T或其子类,避免类型错误。

代码示例

生产者场景(? extends T
import java.util.Arrays;
import java.util.List;

public class ProducerExample {
    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3);
        List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
        
        // 生产者方法:接收任何Number或其子类的List
        processNumbers(integers);  // 编译通过,Integer是Number的子类
        processNumbers(doubles);  // 编译通过,Double是Number的子类
    }

    // 生产者方法:只读取数据,不写入
    public static void processNumbers(List<? extends Number> numbers) {
        for (Number num : numbers) {
            System.out.println(num);  // 安全读取,类型为Number或其子类
        }
        // numbers.add(1);  // 编译错误,无法添加非null元素
    }
}

说明

  • List<? extends Number>可接受List<Integer>List<Double>等,但无法调用add()方法(编译器无法确定具体类型)。
  • 读取时可用Number或其父类(如Object)接收,确保类型安全。
消费者场景(? super T
import java.util.ArrayList;
import java.util.List;

public class ConsumerExample {
    public static void main(String[] args) {
        List<Object> objects = new ArrayList<>();
        List<Number> numbers = new ArrayList<>();
        
        // 消费者方法:接收任何Number或其父类的List
        addNumbers(objects);  // 编译通过,Object是Number的父类
        addNumbers(numbers);  // 编译通过,Number是自身父类
    }

    // 消费者方法:只写入数据,不读取
    public static void addNumbers(List<? super Number> numbers) {
        numbers.add(1);       // 安全添加Integer(Number的子类)
        numbers.add(1.1);     // 安全添加Double(Number的子类)
        // Number num = numbers.get(0);  // 编译错误,只能用Object接收
        Object obj = numbers.get(0);  // 只能用Object接收,类型信息丢失
    }
}

说明

  • List<? super Number>可接受List<Object>List<Number>等,但读取时只能用Object接收(编译器无法确定具体类型)。
  • 添加时可用Number或其子类(如IntegerDouble),确保类型兼容。

应用场景

  • 集合拷贝(Collections.copy
    JDK中的Collections.copy方法同时使用? extends T? super T
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        // dest是消费者(只写),src是生产者(只读)
    }
    • src作为生产者,使用? extends T确保读取的数据是T或其子类。
    • dest作为消费者,使用? super T确保写入的数据是T或其子类。
  • 栈的批量操作
    自定义栈的pushAllpopAll方法:
    public class Stack<E> {
        // 消费者方法:接收任何E或其子类的Iterable
        public void pushAll(Iterable<? extends E> src) {
            for (E e : src) {
                push(e);
            }
        }
    
        // 生产者方法:将元素弹出到任何E或其父类的Collection
        public void popAll(Collection<? super E> dest) {
            while (!isEmpty()) {
                dest.add(pop());
            }
        }
    }
    • pushAllsrc是生产者,使用? extends E确保读取的数据是E或其子类。
    • popAlldest是消费者,使用? super E确保写入的数据是E或其子类。

总结

场景通配符类型约束允许操作典型用途
生产者(只读)? extends T元素类型为T或其子类安全读取,禁止写入方法参数、集合拷贝源
消费者(只写)? super T元素类型为T或其父类安全写入,限制读取方法参数、集合拷贝目标

PECS法则的本质是通过类型边界约束,在编译期确保生产者-消费者模式的类型安全,避免运行时ClassCastException

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jack_abu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值