详细介绍一下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 外,上界/无界通配符基本禁止写入,避免类型转换异常。
相关推荐
能喵烧香2 小时前
深度解析:Linux 与 Windows 超级权限账户的本质差异
linux·windows
JosieBook2 小时前
【数据库】时序预测能力的分级进化:TimechoAI如何让每一类用户都能精准预见未来
java·开发语言·数据库
小帅热爱难回头2 小时前
编写Skill生成AI落地项目系统架构
python
diving deep3 小时前
脚本速览-python
开发语言·python
一生了无挂3 小时前
Java处理JSON技巧教学(从基础到高阶实战全覆盖)
java·开发语言·json
李白的天不白4 小时前
使用 SmartAdmin 进行前后端开发
java·前端
swordbob4 小时前
Spring 单例 Bean 是线程安全的吗?
java·开发语言
2601_951643774 小时前
Python第一,Java跌出前三,C语言杀回来了
java·c语言·python·编程语言排行·技术趋势
caimouse6 小时前
Reactos 第 7 章 视窗报文 — 7.5 视窗报文的发送
windows