通配符 “?“

在 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 更灵活,但也会限制能做的操作
相关推荐
方也_arkling19 小时前
【Java-Day08】static / final / 枚举
java·开发语言
橙淮20 小时前
Spring Bean作用域与生命周期全解析
java·spring
Chengbei1120 小时前
一站式源码安全检测工具、云安全 / APP / 小程序源码敏感信息递归多层目录扫描AK、JWT、手机号、身份证等敏感信息
java·开发语言·安全·web安全·网络安全·系统安全·安全架构
llz_11220 小时前
web-第一次课后作业
java·开发语言·idea
秋920 小时前
Java项目运行5天左右自动宕机:系统性定位与解决方案
java·开发语言·python
小江的记录本20 小时前
【JVM虚拟机】垃圾回收GC:垃圾收集器:CMS:核心原理、回收流程、优缺点、废弃原因(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·spring·面试·maven
DIY源码阁21 小时前
JavaSwing学生成绩管理系统 - MySQL版
java·数据库·mysql·eclipse
basketball6161 天前
C++ NULL 和 nullptr 区别 以及 nullptr 的核心实现
java·开发语言·c++
JAVA面经实录9171 天前
MyBatis面试题库
java·mybatis
小江的记录本1 天前
【JVM虚拟机】垃圾回收GC:垃圾回收算法:标记-清除、标记-复制、标记-整理、分代收集(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·算法·安全·面试