详细介绍一下Java泛型的通配符

Java 泛型通配符 详细讲解

泛型通配符使用 ? 表示未知类型 ,主要用于解决泛型参数的多态兼容 问题,分为无界通配符、上界通配符、下界通配符三类,同时结合读写规则、使用场景、面试考点逐一说明。

前置结论:

  1. List<String> 不是 List<Object> 的子类,泛型本身不支持协变;
  2. 通配符就是为了打破泛型集合无法向上兼容的限制;
  3. 核心记忆:上界读优先、下界写优先、无界只读

一、为什么需要通配符?

先看一个报错案例,理解痛点:

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class Test {
    // 要求接收 List<Object>
    public static void printList(List<Object> list) {
        for (Object o : list) {
            System.out.println(o);
        }
    }

    public static void main(String[] args) {
        List<String> strList = new ArrayList<>();
        strList.add("Java");
        // 编译报错:List<String> 不能赋值给 List<Object>
        printList(strList);
    }
}

原因:泛型没有多态List<String>List<Object> 是两个完全独立的类型,不存在父子关系。

此时就需要通配符 ? 来表示"任意未知类型",实现参数兼容。


二、无界通配符 <?>

1. 定义

<?> 代表完全未知的任意类型 ,不做任何类型限制。

语法:集合<?> / 类型<?>

2. 适用场景

只读取集合元素、不向集合写入数据,且不关心集合具体存储什么类型时使用。

3. 读写规则(重点)

  • :可以读取元素,读取后类型统一为 Object
  • 除了 null,不能写入任何对象(编译器无法判断目标类型,禁止写入,避免类型安全问题)。

4. 代码示例

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class WildcardNone {
    // 无界通配符:接收任意类型的 List
    public static void show(List<?> list) {
        // 读取:元素转为 Object
        for (Object obj : list) {
            System.out.println(obj);
        }

        // 写入:只能写 null,其余全部编译报错
        list.add(null);
        // list.add("abc");  // 报错
        // list.add(123);    // 报错
    }

    public static void main(String[] args) {
        List<String> strList = new ArrayList<>();
        strList.add("泛型");
        List<Integer> intList = new ArrayList<>();
        intList.add(666);

        show(strList);
        show(intList);
    }
}

5. 总结

  • 用途:通用遍历、查看数据;
  • 特点:只读为主,禁止写入(null除外)

三、上界通配符 <? extends T>

1. 定义

限定类型为 T 本身 或 T 的子类 ,也叫上限通配符

语法:<? extends 父类/接口>

2. 继承关系示例

假设类继承:Number ← IntegerNumber ← Double

  • List<? extends Number> 可以接收:List<Number>List<Integer>List<Double>

3. 读写规则(核心考点)

  1. 读操作 :允许读取,取出的元素可以赋值给 父类 T(安全);
  2. 写操作完全禁止写入非 null 数据 。 原因:编译器无法确定集合真实子类型。比如 List<? extends Number> 可能是 List<Integer>,也可能是 List<Double>,强行写入会引发类型异常。

4. 代码示例

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class WildcardExtends {
    // 上界通配符:只接收 Number 及其子类
    public static void printNum(List<? extends Number> list) {
        // 读取:安全,用 Number 接收
        for (Number num : list) {
            System.out.println(num);
        }

        // 写入:全部报错
        // list.add(100);
        // list.add(3.14);
        list.add(null); // 仅允许 null
    }

    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        intList.add(10);
        List<Double> douList = new ArrayList<>();
        douList.add(3.14);

        printNum(intList);
        printNum(douList);
    }
}

5. 适用场景

从集合中读取数据,元素有统一父类,典型场景:数据查询、遍历、统计。


四、下界通配符 <? super T>

1. 定义

限定类型为 T 本身 或 T 的父类 ,也叫下限通配符

语法:<? super 子类>

2. 继承关系示例

依旧以 Integer → Number → Object 为例:

  • List<? super Integer> 可以接收:List<Integer>List<Number>List<Object>

3. 读写规则(核心考点)

  1. 写操作允许写入 T 及其子类对象(安全);
  2. 读操作 :可以读取,但取出的元素只能用 Object 接收(无法确定具体父类)。

4. 代码示例

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class WildcardSuper {
    // 下界通配符:接收 Integer 及其父类
    public static void addNum(List<? super Integer> list) {
        // 写入:允许 Integer / Integer 子类
        list.add(100);
        list.add(200);

        // 读取:只能用 Object 接收
        for (Object obj : list) {
            System.out.println(obj);
        }

        // Integer num = list.get(0); // 编译报错
    }

    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        List<Number> numList = new ArrayList<>();
        List<Object> objList = new ArrayList<>();

        addNum(intList);
        addNum(numList);
        addNum(objList);
    }
}

5. 适用场景

向集合中写入数据,典型场景:数据填充、元素添加、批量存入对象。


五、三种通配符对比表

通配符格式 含义 可读 可写 典型使用场景
<?> 无界 任意未知类型 ✅ 读为 Object ❌ 仅可写 null 通用遍历、查看数据
<? extends T> 上界 T 或 T 的子类 ✅ 读为 T ❌ 仅可写 null 读取数据、查询、遍历
<? super T> 下界 T 或 T 的父类 ✅ 只能读为 Object ✅ 可写入 T/子类 写入数据、填充集合

六、高频考点 & 拓展知识

1. PECS 原则(面试必问)

PECS:Producer Extends, Consumer Super

  • 生产者(只往外读数据) :使用 <? extends T>
  • 消费者(只往里写数据) :使用 <? super T>
  • 既要读又要写:不要使用通配符 ,直接使用确定泛型 <T>

2. 通配符不能和泛型边界混用的误区

  • 可以组合:public <T extends Number> void test(List<T> list)(泛型方法边界)
  • 区分:泛型边界 <T extends> 是定义类型范围;通配符 <? extends> 是接收参数范围。

3. 通配符与泛型数组

Java 不支持泛型数组,通配符数组也有诸多限制,日常开发尽量避免。

4. 通配符不能用于泛型方法返回值

java 复制代码
// 不推荐、可读性极差,业务中禁止使用
public List<?> getList(){...}

返回值尽量使用确定泛型 <T>,不要用通配符。


七、综合实战案例(PECS 落地)

需求:实现一个工具方法,将源集合元素拷贝到目标集合

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class CopyDemo {
    /**
     * 拷贝集合
     * 源集合:只读取 → Producer → <? extends T>
     * 目标集合:只写入 → Consumer → <? super T>
     */
    public static <T> void copy(List<? extends T> src, List<? super T> dest) {
        for (T t : src) {
            dest.add(t);
        }
    }

    public static void main(String[] args) {
        List<Integer> src = new ArrayList<>();
        src.add(1);
        src.add(2);

        List<Number> dest1 = new ArrayList<>();
        List<Object> dest2 = new ArrayList<>();

        copy(src, dest1);
        copy(src, dest2);

        System.out.println(dest1);
        System.out.println(dest2);
    }
}

这是 JDK 中 Collections.copy 的核心设计思想,完美体现 PECS 原则。


八、总结

  1. 通配符 ? 解决泛型集合无法多态兼容的问题;
  2. 三大类型:
    • <?>:任意类型,只读
    • <? extends T>:上界(子类),适合读
    • <? super T>:下界(父类),适合写
  3. 牢记 PECS 原则:读用 extends,写用 super,读写都用则不用通配符;
  4. 核心限制:除 null 外,上界/无界通配符基本禁止写入,避免类型转换异常。
相关推荐
啦啦啦~~~2227 小时前
PC端+安卓端阅读器推荐!开源本地小说阅读器软件,
android·论文阅读·windows·开源软件·福昕阅读器
2601_962297257 小时前
Python、Pytest、Allure、Selenium和Jenkins实现自动化测试集成实例
python·selenium·jenkins·pytest·allure
霸道流氓气质7 小时前
Spring AI 技术细节:VectorStore 多库统一抽象
java·人工智能·spring
今天AI了吗7 小时前
Codex 配置自定义 AI API 完整指南:从零到一接入你的专属模型
java·人工智能·python·数据分析·embedding
阿童木写作7 小时前
跨境电商批量图片翻译与视频字幕翻译工具推荐
python·音视频
leihefeng7 小时前
手写数字识别:KNN vs 逻辑回归实战
python·算法·机器学习·逻辑回归·scikit-learn
学长毕业设计8 小时前
基于SpringBoot的健康食谱管理系统的设计与实现(源码+文档+讲解视频)
java·spring boot·后端
珍珠先生8 小时前
P10 · 缺 MySQL 驱动 jar:ClassNotFoundException: com.mysql.cj.jdbc.Driver
java
SunnyDays10118 小时前
如何使用 Python 从 Word 文档中提取图片
python·word
2601_962097368 小时前
Pathway 实时RAG新方案:抛弃Spark、Flink,生产级数据管道
python·流处理·大数据集群·实时rag·pathway