通配符 “?“

在 Java 泛型中,? 被称为通配符(Wildcard)。它用来表示"未知的类型",主要在声明变量或参数时使用,以增加代码的灵活性。

三种主要形式

1. 无界通配符(Unbounded Wildcard):<?>

表示"可以是任何类型",但因为你不知道具体是什么类型,只能从中读取 Object 类型,不能往里添加元素 (除了 null)。

java 复制代码
public void printList(List<?> list) {
    Object obj = list.get(0);      // ✅ 可以读,返回 Object
    // list.add("hello");          // ❌ 编译错误!不能添加任何元素
    list.add(null);                // ✅ 特例,可以加 null
}

适用场景:不关心具体类型,仅用 Object 提供的方法就能处理。比如打印所有元素、判断是否为空。

2. 上界通配符(Upper Bounded Wildcard):<? extends T>

表示"类型是 T 或 T 的子类"。适用于读取场景------从结构中获取数据时,可以保证得到 T 类型。

java 复制代码
public double sum(List<? extends Number> numbers) {
    double total = 0.0;
    for (Number n : numbers) {    // ✅ 可以读,确保是 Number 或其子类
        total += n.doubleValue();
    }
    // numbers.add(1);              // ❌ 编译错误!不能添加具体元素
    return total;
}
// 调用
sum(List.of(1, 2, 3));          // Integer 子类
sum(List.of(1.0, 2.5, 3.8));    // Double 子类

限制 :只允许读取 ,不能写入(除了 null)。因为编译器不知道具体的子类型是什么(可能是 IntegerDouble 等),无法保证类型安全。

3. 下界通配符(Lower Bounded Wildcard):<? super T>

表示"类型是 T 或 T 的父类"。适用于写入场景------可以向结构中添加 T 类型的对象,但读取时只能得到 Object。

java 复制代码
public void addNumbers(List<? super Integer> list) {
    list.add(100);                // ✅ 可以添加 Integer
    list.add(200);
    // Integer num = list.get(0); // ❌ 编译错误!不能直接读成 Integer
    Object obj = list.get(0);     // ✅ 只能作为 Object 读取
}
// 调用
List<Number> list1 = new ArrayList<>();
List<Object> list2 = new ArrayList<>();
addNumbers(list1);  // Number 是 Integer 的父类 ✅
addNumbers(list2);  // Object 也是父类 ✅
// List<Integer> list3 = ...    // ❌ Integer 不是 Integer 的父类

规则

  • 可以写:T 及 T 的子类(但实践中通常只写 T 本身)
  • 可以读:只能当作 Object 来读(因为父类型可能是 Object、Number 等不确定)

经典使用场景:PECS 原则

PECS = Producer Extends, Consumer Super

这是 Joshua Bloch 在《Effective Java》中提出的记忆口诀:

  • Producer (生产者) :如果参数提供数据给你读 → 用 <? extends T> 上界
  • Consumer (消费者) :如果参数消费你提供的数据(你往里写)→ 用 <? super T> 下界
java 复制代码
// Producer: 从集合中拷贝数据出去(读)
public void copy(List<? extends Number> source, List<? super Number> dest) {
    for (Number n : source) {   // source 是生产者,用 extends
        dest.add(n);            // dest 是消费者,用 super
    }
}

常见误区对比

写法 含义 能读 能写
List<T> 具体类型 T T 类型 T 类型
List<?> 未知类型 Object 只能 null
List<? extends Number> 某个 Number 子类 Number 不能(除 null)
List<? super Integer> 某个 Integer 父类 Object Integer 及子类

关键区别:<T><?>

java 复制代码
// 类型参数 T:可以在多处保持一致
public <T> void swap(List<T> list, int i, int j) {
    T temp = list.get(i);   // 类型确定
    list.set(i, list.get(j));
    list.set(j, temp);
}

// 通配符 ?:不关心彼此是否一致
public void print(List<?> list) {
    // 没法保证 list.get(0) 和 list.get(1) 是同一类型,但这里不需要
}

原则

  • 声明泛型类/方法 时用 <T>(类型参数)
  • 声明变量/参数 类型时用 <?>(通配符)

实际应用举例

java 复制代码
// ✅ 好的用法:灵活接收各类集合
public void sort(List<? extends Comparable<?>> list) { ... }

// ✅ 结合集合工具类
Collections.copy(dest, src);  // dest: <? super T>, src: <? extends T>

// ❌ 错误:不能同时有上界和下界
// List<? extends Number super Integer>  // 语法不支持

// ❌ 错误:不能用在 new 泛型实例上
// List<?> list = new ArrayList<?>();     // 编译错误
List<?> list = new ArrayList<String>();   // ✅ 但右边需具体类型

总结

  • <?>:我不知道是什么类型
  • <? extends T>:它是 T 或 T 的子类(主要用来读)
  • <? super T>:它是 T 或 T 的父类(主要用来写)
  • PECSextends 适合 Producer(读),super 适合 Consumer(写)
  • 通配符让 API 更灵活,但也会限制能做的操作
相关推荐
李斯维1 小时前
工厂设计模式(Factory Pattern):工厂方法与抽象工厂的实例演示
java·设计模式
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题 第41题】【JVM篇】第1题:JVM由哪些部分组成?
java·开发语言·jvm·后端·面试
0xDevNull1 小时前
ConcurrentHashMap 与 Hashtable 深度对比
java·开发语言
happymaker06261 小时前
Spring学习日记——Day01(简单配置使用Spring,手写Spring的简单工厂模式)
java·学习·spring
木易 士心1 小时前
深度解析:一个 Java 对象究竟占用多少字节?
java·开发语言·后端
夜猫子ing1 小时前
《嵌入式 Linux 控制服务从零搭建(二):从目录结构到 CMakeLists,搭一个像样的 C++ 工程骨架》
java·前端·c++
人道领域2 小时前
【LeetCode刷题日记】二叉树翻转:递归与迭代全解析
java·算法·leetcode
Cyan_RA92 小时前
SpringMVC 视图和视图解析器 万字详解
java·spring·mvc·springmvc·请求重定向·modelandview·视图解析器
想学习java初学者11 小时前
SpringBoot整合Vertx-Mqtt多租户(优化版)
java·spring boot·后端