在 Java 泛型体系中,? extends T(上界通配符)和 ? super T(下界通配符)是处理泛型集合灵活性与类型安全的核心语法。本文将从添加规则、读取方式、强制转换三个维度,结合实例深入解析这两种通配符的特性,并总结其实际应用场景。
一、上界通配符:List<? extends A>
核心特性:「只读难加"
上界通配符限定集合元素为 A 或 A 的子类(如 A1、A2),但编译器无法确定集合具体存储的是哪个子类,因此除了 null,无法添加任何对象;读取时则可通过 A 或 Object 向上转型安全获取。
完整示例与解析
java
import java.util.ArrayList;
import java.util.List;
public class GenericExtendsDemo {
// 定义继承体系
public static class Parent {}
public static class A extends Parent {}
public static class A1 extends A {} // A的子类1
public static class A2 extends A {} // A的子类2
public static void main(String[] args) {
// 声明上界通配符集合:元素为A或A的子类
List<? extends A> extendsList = new ArrayList<>();
// ========== 添加操作:编译错误分析 ==========
// 错误1:无法添加A的子类A1
// 原因:编译器无法确定extendsList实际存储的是A1还是A2,若允许添加A1,后续添加A2会破坏类型一致性
extendsList.add(new A1()); // 编译错误
// 错误2:无法添加父类A本身
// 原因:上界通配符的本质是「未知的A子类集合」,A并非"未知子类",向下转型不安全
extendsList.add(new A()); // 编译错误
// 唯一例外:null是所有引用类型的默认值,无类型冲突
extendsList.add(null);
// ========== 读取操作:安全向上转型 ==========
// 方式1:用A接收(最常用)
A a = extendsList.get(0);
// 方式2:用Object接收(所有对象的根类型)
Object obj = extendsList.get(0);
}
}
关键原理
上界通配符的核心是「协变」:集合类型可从 List<A1> 安全转型为 List<? extends A>,但代价是失去添加能力 ------ 因为添加操作会打破协变的类型安全。
二、下界通配符:List<? super A>
核心特性:「可加限定,读取需强转"
下界通配符限定集合元素为 A 或 A 的父类(如 Parent、Object),编译器能确定 A 及其子类可向上转型为任意父类,因此可添加 A 或 A 的子类;读取时只能默认用 Object 接收,若需用 A 接收则需强制类型转换。
完整示例与解析
java
import java.util.ArrayList;
import java.util.List;
public class GenericSuperDemo {
// 复用继承体系
public static class Parent {}
public static class A extends Parent {}
public static class A1 extends A {}
public static class A2 extends A {}
public static void main(String[] args) {
// 声明下界通配符集合:元素为A或A的父类
List<? super A> superList = new ArrayList<>();
// ========== 添加操作:编译规则分析 ==========
// 错误1:无法添加A的父类Parent
// 原因:编译器无法确定superList实际存储的是Parent还是Object,添加Parent会破坏类型一致性
superList.add(new Parent()); // 编译错误
// 错误2:无法添加顶级父类Object
// 原因:同理,Object并非"已知的A父类",向上转型超出下界限定
superList.add(new Object()); // 编译错误
// 正确1:添加A本身
// 原因:A可向上转型为任意A的父类(Parent/Object),符合下界限定
superList.add(new A());
// 正确2:添加A的子类A1/A2
// 原因:A1/A2可先向上转型为A,再转型为A的父类,类型安全
superList.add(new A1());
// 例外:null仍可添加
superList.add(null);
// ========== 读取操作:需处理类型转换 ==========
// 方式1:默认用Object接收(编译器仅能确定元素是Object的子类)
Object obj = superList.get(0);
// 方式2:强制转换为A(需确保集合中实际存储的是A或其子类,否则抛ClassCastException)
A a = (A) superList.get(0);
}
}
关键原理
下界通配符的核心是「逆变」:集合类型可从 List<Parent> 安全转型为 List<? super A>,添加 A 及其子类时,因向上转型的特性保证了类型安全;但读取时无法确定具体类型,只能退化为 Object。
三、泛型集合的强制转换:边界限定规则
泛型集合的强制转换并非任意,需严格遵循 extends/super 的边界限定 ------ 仅能转换为边界类型或满足边界条件的类型,否则编译器直接报错。
完整示例与解析
java
import java.util.ArrayList;
import java.util.List;
public class GenericCastDemo {
public static class Parent {}
public static class A extends Parent {}
public static class A1 extends A {}
public static void main(String[] args) {
// ========== 场景1:上界通配符 List<? extends Parent> 的转换 ==========
List<? extends Parent> extendParentList = new ArrayList<>();
// 正确:转换为边界类型 Parent(符合"Parent或其子类"的限定)
List<Parent> parentList1 = (List<Parent>) extendParentList;
// 错误:Object不是Parent的子类,违反上界限定(extends Parent)
List<Object> objectList1 = (List<Object>) extendParentList; // 编译或运行时错误
// ========== 场景2:上界通配符 List<? extends Object> 的转换 ==========
List<? extends Object> extendObjectList = new ArrayList<>();
// 正确:Object是所有类的父类,Parent符合"Object或其子类"的限定
List<Parent> parentList2 = (List<Parent>) extendObjectList;
// ========== 场景3:下界通配符 List<? super Parent> 的转换 ==========
List<? super Parent> superParentList = new ArrayList<>();
// 正确:转换为边界类型 Parent(符合"Parent或其父类"的限定)
List<Parent> parentList3 = (List<Parent>) superParentList;
// 正确:Object是Parent的父类,符合下界限定(super Parent)
List<Object> objectList2 = (List<Object>) superParentList;
// ========== 场景4:普通泛型集合的转换(无通配符) ==========
List<Object> rawObjectList = new ArrayList<>();
// 错误:无通配符的 List<Object> 无法直接转为 List<Parent>
// 原因:泛型不协变,List<Object> 和 List<Parent> 是完全独立的类型
List<Parent> parentList4 = (List<Parent>) rawObjectList; // 不建议
}
}
转换规则总结
| 通配符类型 | 可转换类型 | 不可转换类型 |
|---|---|---|
List<? extends T> |
T 或 T 的子类 | T 的父类(除非 T 是 Object) |
List<? super T> |
T 或 T 的父类 | T 的子类 |
无通配符 List<T> |
仅 T 本身(强制转换可能抛运行时异常) | 其他所有类型(编译器直接报错) |
四、实战总结与应用场景
核心结论
1.通配符的核心价值:? extends/super 主要用于方法参数定义,允许接收更宽泛的集合类型,提升方法的通用性。 示例:定义一个打印 A 及其子类的方法
java
// 用 extends 接收所有 A 的子类集合,只读不写
public static void printA(List<? extends A> list) {
for (A a : list) {
System.out.println(a);
}
}
// 用 super 接收所有 A 的父类集合,只写不读(或读取后强转)
public static void addA(List<? super A> list) {
list.add(new A1()); // 安全添加 A 的子类
}
2.方法参数的入参规则:传入的集合类型需符合通配符的边界限定,与前文的添加 / 转换规则完全一致。 3.设计原则:
- 「生产者(只读)用 extends」:若方法仅读取集合元素,用
? extends T; - 「消费者(只写)用 super」:若方法仅添加集合元素,用
? super T; - 「既读又写」:避免使用通配符,直接用具体类型
List<T>。
避坑提醒
- 强制转换泛型集合时,编译器仅做语法检查,运行时可能抛出
ClassCastException(如将List<A1>强转为List<A2>); null虽可添加到任意泛型集合,但实际开发中应避免(读取时需判空,否则抛NullPointerException);- 泛型通配符仅作用于方法参数,不建议用于类成员变量或返回值(会丢失类型信息)。
通过理解 extends/super 的边界限定和类型安全规则,可在保证代码灵活性的同时,避免泛型带来的类型转换异常,写出更健壮的 Java 泛型代码。