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
或其子类(如Integer
、Double
)的列表 - 下界通配符:
List<? super Integer>
—— 表示Integer
或其父类(如Number
、Object
)的列表
这里有个常见的误解:既然 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”,否则一律用
<?>
,更简洁也更安全。