1. 概述

本文将深入探讨 Java 泛型中的两个常见通配符形式:<?><? extends Object>,分析它们的相似之处与关键差异

⚠️ 这是一个进阶话题,如果你对泛型的基础概念(如类型擦除、通配符上下界)还不熟悉,建议先补一补基础。本文不打算从“什么是泛型”讲起,毕竟读者不是初学者。

2. 泛型的背景

泛型在 JDK 5 中引入,主要目的有两个:✅ 提升类型安全,❌ 避免运行时类型转换错误。

在泛型出现之前,我们操作集合时常常需要手动强转,稍不注意就会在运行时抛出 ClassCastException

List aList = new ArrayList();
aList.add(new Integer(1));
aList.add("a_string");
        
for (int i = 0; i < aList.size(); i++) {
    Integer x = (Integer) aList.get(i); // 第二次循环直接炸
}

上面这段代码有两个明显问题:

  • ✅ 需要显式类型转换,代码啰嗦且易错
  • "a_string" 被错误地加入 Integer 列表,编译器无法发现,直到运行时报错

而使用泛型后,问题迎刃而解:

List<Integer> iList = new ArrayList<>();
iList.add(1);
iList.add("a_string"); // ❌ 编译报错:String 无法添加到 List<Integer>

for (int i = 0; i < iList.size(); i++) {
    int x = iList.get(i); // ✅ 自动拆箱,无需强转
}

编译器在编译期就能拦截非法类型操作,同时得益于自动拆箱(unboxing),连 Integer 都可以直接赋值给 int,简洁又安全。

3. 泛型中的通配符

通配符(?)用于表示未知类型,主要有三种形式:

  • 无界通配符List<?> —— 表示一个未知类型的列表
  • 上界通配符List<? extends Number> —— 表示 Number 或其子类(如 IntegerDouble)的列表
  • 下界通配符List<? super Integer> —— 表示 Integer 或其父类(如 NumberObject)的列表

这里有个常见的误解:既然 Object 是所有类的根父类,那 List<?>List<Object> 是不是等价?完全不是

来看两个方法:

public static void printListObject(List<Object> list) {    
    for (Object element : list) {        
        System.out.print(element + " ");    
    }        
}    

public static void printListWildCard(List<?> list) {    
    for (Object element: list) {        
        System.out.print(element + " ");    
    }     
}

假设我们有一个 Integer 列表:

List<Integer> li = Arrays.asList(1, 2, 3);
  • printListObject(li) —— 编译失败!
    错误信息:

    The method printListObject(List<Object>) is not applicable for the arguments (List<Integer>)
    
  • printListWildCard(li) —— 编译通过,输出 1 2 3

原因很简单:List<Integer> 不是 List<Object> 的子类型,Java 泛型不支持协变(除非使用通配符)。而 List<?> 可以接受任意类型的 List,这才是它的设计本意。

4. <?> 与 <? extends Object> 的相似性

如果我们把 printListWildCard 的参数改为:

public static void printListWildCard(List<? extends Object> list)

你会发现行为完全一样,依然能接收 List<Integer> 并正常输出。

✅ 为什么?因为所有 Java 对象都隐式继承自 Object,所以 ? extends Object 实际上涵盖了所有引用类型。

在绝大多数使用场景下,<?><? extends Object> 的表现是等价的

但注意,这只是“看起来一样”,底层实现和类型可重化性(reifiable)上存在关键差异。

5. <?> 与 <? extends Object> 的差异

核心区别在于:类型是否可重化(reifiable)

🔍 什么是 reifiable?
简单说,就是该类型在运行时仍然保留完整的泛型信息,不会被类型擦除完全抹掉。

根据 Java 规范:

  • 无界通配符类型是可重化的List<?>Map<?, ?> 在运行时仍被视为“带泛型的类型”
  • 有界通配符类型不可重化List<? extends Object> 会被完全擦除,视为原始类型 List

这个差异在某些特定场景下会“踩坑”:

5.1 instanceof 不支持非可重化类型

以下代码 ✅ 可以编译通过:

List someList = new ArrayList<>();
boolean instanceTest = someList instanceof List<?>;

但如果你换成 <? extends Object>

List anotherList = new ArrayList<>();
boolean instanceTest = anotherList instanceof List<? extends Object>; // ❌ 编译错误

编译器会报错:**Illegal generic type for instanceof**。

5.2 数组创建限制

同样,非可重化类型不能用于数组元素类型:

List<?>[] arrayOfList = new List<?>[1]; // ✅ 合法
List<? extends Object>[] arrayOfAnotherList = new List<? extends Object>[1]; // ❌ 编译错误

第二行会提示:**Generic array creation**。

⚠️ 虽然日常开发中很少直接用到 instanceof List<?> 或泛型数组,但一旦遇到,这个差异就会成为关键障碍。

6. 结论

对比项 <?> <? extends Object>
是否可重化 ✅ 是 ❌ 否
能否用于 instanceof ✅ 可以 ❌ 不可以
能否作为数组元素类型 ✅ 可以 ❌ 不可以
实际类型覆盖范围 所有引用类型 所有引用类型(理论上)
日常使用差异 基本无感 基本无感

✅ 总结:

  • 在大多数场景下,<?><? extends Object> 行为一致,可以互换
  • 但在涉及 类型检查(instanceof)数组创建 时,<?> 是唯一合法选择
  • 因此,**推荐统一使用 <?>**,简单粗暴,避免潜在陷阱

💡 小建议:除非有特殊语义需要强调“继承自 Object”,否则一律用 <?>,更简洁也更安全。


原始标题:Java Generics – vs