通配符 “?“

在 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 更灵活,但也会限制能做的操作
相关推荐
lhldsg2 小时前
全民健身解决方案小程序开发:从0到1的技术实战
java·数据库·需求分析
jason成都2 小时前
Spring WebFlux 适配达梦新方案|dm‑r2dbc:Netty 异步传输的实验性 R2DBC 驱动
java·后端·spring
泡海椒3 小时前
内置SPI函数库详解:JQuick-Java Builtin工具类实战用法
java·开发语言·python
辰烨chenye3 小时前
LeetCode Hot 100 题解 · 二分篇
java·算法·leetcode
小羊没烦恼!3 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
小荷才露尖尖角,早有蜻蜓立上头4 小时前
过滤器的4种创建方式
java
大圣编蚕5 小时前
Java ByteArrayInputStream 详解:从入门到实战
java·开发语言·算法
程序员阿明5 小时前
idea把插件啥的不默认放在C盘
java·ide·intellij-idea
linweidong5 小时前
中科闻歌Java面试题及参考答案
java·python·大模型·提示词·ai agent·mcp·rag原理
OpenAnolis小助手6 小时前
一句话看透 JVM,SysOM 诊断 Skill 新增 Java 应用诊断能力
java·开发语言·jvm·阿里云·操作系统控制台·sysom