Java泛型通配符深度解析:无限定、上界、下界通配符全面把握

[复制链接]
发表于 2026-10-3 11:02:51 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×

Java 泛型通配符是个看着简单、用错就报错的东西。它有三种形态:无限定通配符 ?、上界通配符 ? extends T、下界通配符 ? super T。这篇分别讲清楚它们解决什么问题、代码怎么写、以及哪些操作会被编译器拦下来。

一、为什么需要通配符

泛型本身有不变性:List<String> 和 List<Object> 是两个完全不同的类型,List<String> 不能传给需要 List<Object> 的方法。但开发里经常碰到这种需求——"我不管你列表里装的是什么,只要能逐个读出来打印就行"。这时候写死一个具体类型就不现实。

通配符就是干这个的:表示"任意类型的泛型",让方法能接收一类泛型而不是某一个。

二、无限定通配符 ?

写法就是 List<?>,表示这个列表可以装任何类型的对象。典型场景:只关心元素的共性(都是 Object 的实例),不关心具体类型。

import java.util.Arrays;
import java.util.List;

public class UnboundedWildcard {
    // 用无限定通配符接收任意类型的 List
    public static void printList(List<?> list) {
        for (Object item : list) {
            System.out.println(item);
        }
    }

    public static void main(String[] args) {
        List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
        List<String> stringList = Arrays.asList("One", "More");

        // Integer 列表和 String 列表都能进这个方法
        printList(integerList);
        printList(stringList);
    }
}

关键限制:用 List<?> 做形参时,方法里不能往这个列表里 add 任何非 null 元素——编译器不知道你到底要装 String 还是 Integer,为了类型安全直接禁止。读取则没问题,因为统统当成 Object。

三、上界通配符 ? extends T

用 extends 限定上限:接收的是 T 以及 T 的子类。适用场景——你需要处理的对象有个共同父类,只想调用父类上的方法。

class Animal {
    public void eat() {
        System.out.println("Animal is eating...");
    }
}

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

public class Test {
    // 能接收任何 Animal 子类组成的列表
    public static void feedAnimals(List<? extends Animal> animals) {
        for (Animal a : animals) {
            a.eat();
        }
    }
}

好处一目了然:List<Dog>、List<Cat>、List<Animal> 都能传进来,方法内部按 Animal 调 eat() 就行,不用为每种子类各写一个重载。

对应的限制依然是:只能读,不能写(除了 null)。因为编译器只知道元素至少是 Animal,不知道具体是哪个子类,往里塞 Dog 可能就违反栈里真实的类型。

四、下界通配符 ? super T

用 super 限定下限:接收的是 T 以及 T 的父类。这个方向反过来,适合"只写不读"的场景——我往里塞 T,你别管我具体塞什么。

public class Test {
    // 能接收 Integer 及其父类组成的列表
    public static void addNumbers(List<? super Integer> list) {
        list.add(1);
        list.add(2);
    }
}

List<Integer>、List<Number>、List<Object> 都能传。这条规则有个专门的名字叫 PESCO:Producer extends, Consumer super——生产者(只读的)用 extends,消费者(只写的)用 super。

五、三种通配符对比

  • <?>:任意类型,只读不写,适合纯消费场景。
  • <? extends T>:T 及其子类,安全读取,不能 add 非 null。
  • <? super T>:T 及其父类,安全写入,读取只能当 Object。

顺带提一句,很多人会把 extends 和"能不能写"搞混。记住一条:带 extends 的集合是生产者,你只能从里面取;带 super 的集合是消费者,你只能往里面放。想又读又写,就得用确定的具体类型,比如 List<Animal>。

六、实际怎么选

  • 方法只遍历打印 → <?> 就够了;
  • 要调用父类方法、按父类统一处理 → <? extends T>;
  • 往集合里批量塞数据、不关心原内容 → <? super T>;
  • 集合容器类自己实现(比如自定义栈)→ 读侧 extends、写侧 super,这就是 JDK 里很多容器的写法。

总结

泛型通配符的本质是"放宽类型匹配,换取灵活度"。? 万能只读、? extends T 安全读、? super T 安全写,记住 PESCO 这条口诀,写代码时先判断这个集合在你的方法里是生产者还是消费者,通配符方向就不容易写反。

qidao123.com企服评测-应用市场 编辑提示

回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表