10分钟理解泛型的通配符(extends, super, ?)

      • 泛型通配符是什么?
      • 三种基本通配符
        • [1. 无边界通配符:`<?>`](#1. 无边界通配符:<?>)
        • [2. 上界通配符:`<? extends T>`](#2. 上界通配符:<? extends T>)
        • [3. 下界通配符:`<? super T>`](#3. 下界通配符:<? super T>)
      • [PECS原则:Producer Extends, Consumer Super](#PECS原则:Producer Extends, Consumer Super)
      • 实际应用举例
        • [1. 方法参数设计](#1. 方法参数设计)
        • [2. 集合操作工具方法](#2. 集合操作工具方法)
      • 常见误区和注意事项
        • [1. 通配符不能用于创建对象](#1. 通配符不能用于创建对象)
        • [2. 通配符不能作为泛型方法的实际类型参数](#2. 通配符不能作为泛型方法的实际类型参数)
        • [3. 理解边界的方向](#3. 理解边界的方向)
      • 总结

泛型通配符是什么?

在学习Java泛型的过程中,我们经常会遇到三个特殊的符号:?extendssuper。它们组合起来形成的通配符(wildcard)是泛型中最让人头疼的部分,但也是最强大的特性之一。

简单来说,通配符就是用来表示"某种不确定的类型"。就像我们在填表时遇到的"其他"选项一样,当具体类型不重要或者无法确定时,就可以用通配符来代替。

三种基本通配符

1. 无边界通配符:<?>

这是最简单的通配符形式。<?> 表示"某个未知的类型"。

java 复制代码
List<?> list = new ArrayList<String>();  // 可以指向任何类型的List
list = new ArrayList<Integer>();         // 也可以重新赋值为其他类型

使用 <?> 的限制是:你不能向其中添加除了 null 以外的任何对象,因为你不知道实际的类型是什么。

java 复制代码
List<?> list = new ArrayList<String>();
list.add(null);     // 这个可以
// list.add("hello");  // 编译错误!
2. 上界通配符:<? extends T>

这种写法表示"某个继承自 T 的类型",或者说"某种 T 类型或其子类型"。

java 复制代码
List<? extends Number> numbers = new ArrayList<Integer>();  // Integer是Number的子类
numbers = new ArrayList<Double>();   // Double也是Number的子类

为什么要叫"上界"?因为 Number是上限,实际类型必须是 Number或其子类。

使用上界通配符时的限制:

  • 可以安全地从中读取数据(因为读到的肯定能当成 Number处理)
  • 不能向其中写入数据(因为不知道具体是哪种子类型)
java 复制代码
List<? extends Number> numbers = new ArrayList<Integer>();
Number num = numbers.get(0);  // 可以读取,肯定是Number类型
// numbers.add(new Integer(1));  // 编译错误!
3. 下界通配符:<? super T>

这种写法表示"某个 T 的父类型",也就是"某种 T 类型或其父类型"。

java 复制代码
List<? super Integer> integers = new ArrayList<Number>();   // Number是Integer的父类
integers = new ArrayList<Object>();  // Object是所有类的父类

这里 Integer 是下限,实际类型必须是 Integer 或其父类。

使用下界通配符时的特点:

  • 可以安全地向其中写入 T 类型的数据
  • 从中读取 数据时只能当作 Object 处理(因为不知道具体是哪个父类)
java 复制代码
List<? super Integer> integers = new ArrayList<Number>();
integers.add(new Integer(1));    // 可以写入Integer
integers.add(42);                // 自动装箱也行
// Integer num = integers.get(0);   // 错误!只能当Object用
Object obj = integers.get(0);    // 正确!

PECS原则:Producer Extends, Consumer Super

这是使用泛型通配符的重要口诀:

  • Producer Extends :如果你只是从容器中获取数据(生产者),使用 <? extends T>
  • Consumer Super :如果你只是向容器中放入数据(消费者),使用 <? super T>

让我们通过一个具体的例子来理解:

java 复制代码
public static void copy(List<? extends Number> source, 
                       List<? super Number> destination) {
    for (Number num : source) {        // source是生产者,用extends
        destination.add(num);          // destination是消费者,用super
    }
}

在这个例子中:

  • source列表我们只从中读取数据,所以用 <? extends Number>
  • destination列表我们只向其中写入数据,所以用 <? super Number>

实际应用举例

1. 方法参数设计
java 复制代码
// 错误的设计:太严格了
public static void printList(List<Object> list) {
    for (Object item : list) {
        System.out.println(item);
    }
}

// 正确的设计:灵活得多
public static void printList(List<?> list) {
    for (Object item : list) {
        System.out.println(item);
    }
}

// 使用
List<String> strings = Arrays.asList("a", "b", "c");
List<Integer> integers = Arrays.asList(1, 2, 3);

printList(strings);    // 现在可以用了
printList(integers);   // 这个也可以
2. 集合操作工具方法
java 复制代码
// 向一个列表中添加另一个列表的所有元素
public static <T> void addAll(List<? super T> dest, List<? extends T> src) {
    for (T item : src) {
        dest.add(item);
    }
}

// 使用
List<Object> objects = new ArrayList<>();
List<String> strings = Arrays.asList("hello", "world");
addAll(objects, strings);  // 可以把String列表加到Object列表中

常见误区和注意事项

1. 通配符不能用于创建对象
java 复制代码
// 错误!
// List<?> list = new ArrayList<?>();

// 正确的做法
List<?> list = new ArrayList<String>();
2. 通配符不能作为泛型方法的实际类型参数
java 复制代码
public static <T> void method(List<T> list) {
    // ...
}

// 错误!
// method(new ArrayList<?>());

// 正确的做法
List<String> stringList = new ArrayList<>();
method(stringList);
3. 理解边界的方向

很多人容易搞混 extendssuper 的方向:

java 复制代码
// extends: 设置上界(上限)
List<? extends Number>  // 实际类型是Number或其子类

// super: 设置下界(下限)  
List<? super Number>    // 实际类型是Number或其父类

总结

泛型通配符的核心思想是为了增加代码的灵活性:

  1. <?>:完全不知道类型,只能读取为 Object,基本不能写入
  2. <? extends T>:知道是 T 或其子类,可以安全读取为 T,不能写入
  3. <? super T>:知道是 T 或其父类,可以安全写入 T,读取只能当 Object

记住PECS原则,在设计API时合理使用通配符,可以让代码既安全又灵活。刚开始接触时会觉得复杂,但多练习几次就很容易掌握了。

相关推荐
Scott9999HH3 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔3 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社4 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海4 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖5 小时前
JDK 26 新特性详解
java·开发语言
马优晨6 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区8 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大8 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai8 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说8 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json