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\]列表我们只从中读取数据,所以用 ``

实际应用举例

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时合理使用通配符,可以让代码既安全又灵活。刚开始接触时会觉得复杂,但多练习几次就很容易掌握了。

相关推荐
在等晚安么3 小时前
力扣面试经典150题打卡
java·数据结构·算法·leetcode·面试·贪心算法
Fency咖啡3 小时前
Spring进阶 - Spring事务理论+实战,一文吃透事务
java·数据库·spring
Tony Bai3 小时前
【Go模块构建与依赖管理】01 前世今生:从 GOPATH 的“混乱”到 Go Modules 的“秩序”
开发语言·后端·golang
非凡ghost3 小时前
MousePlus(鼠标增强工具) 中文绿色版
前端·windows·计算机外设·软件需求
Zxxxxxy_3 小时前
【MYSQL】增删改查
java·数据库·mysql
菜鸟的迷茫3 小时前
线程池中的坑:线程数配置不当导致任务堆积与拒绝策略失效
java·后端
缺点内向3 小时前
Java 使用 Spire.XLS 库合并 Excel 文件实践
java·开发语言·excel
asdfsdgss3 小时前
多项目共享资源:Ruby 定时任务基于 Whenever 的动态扩缩容
java·网络·ruby
Deamon Tree3 小时前
Redis的过期策略以及内存淘汰机制
java·数据库·redis·缓存