泛型上下限
泛型上下限
泛型上下限
Java 基础 - 泛型机制详解 | Java 全栈知识体系
<?> 无限制通配符
<? extends E> extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类
<? super E> super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类
// 使用原则《Effictive Java》
// 为了获得最大限度的灵活性,要在表示 生产者或者消费者 的输入参数上使用通配符,使用的规则就是:生产者有上限、消费者有下限
1. 如果参数化类型表示一个 T 的生产者,使用 < ? extends T>;
2. 如果它表示一个 T 的消费者,就使用 < ? super T>;
3. 如果既是生产又是消费,那使用通配符就没什么意义了,因为你需要的是精确的参数类型。
为什么 List<? extends A> 不能添加元素?
1. 通配符的实际含义
List<? extends A> 表示:
- "这是一个 List,其元素类型是 某个未知的 A 的子类型"
- 可能是
List<A>,也可能是List<B>,或者List<C>(如果 C 也 extends A)
2. 编译器不知道具体类型
考虑这个场景:
class A {}
class B extends A {}
class C extends A {}
public static void addToList(List<? extends A> list) {
// 编译器不知道 list 的实际类型是什么!
// 可能是 List<A>、List<B> 或 List<C>
// 如果允许这样:
list.add(new B());
// 但调用方可能传入的是 List<C>!
List<C> listC = new ArrayList<>();
addToList(listC); // 如果允许 add,这里就会把 B 对象加入 List<C>!
}
3. 类型安全问题
public static void main(String[] args) {
List<B> listB = new ArrayList<>();
// 假设允许添加
someMethod(listB);
// 现在 listB 中可能有非 B 类型的对象!
B b = listB.get(0); // 可能发生 ClassCastException
}
public static void someMethod(List<? extends A> list) {
// 如果允许:
list.add(new C()); // C 也是 A 的子类,但不是 B!
// 这会破坏 listB 的类型安全
}
三种情况的对比
| 通配符类型 | 含义 | 能否添加 | 能否读取 | 类型安全 |
|---|---|---|---|---|
List<?> |
未知类型 | 不能(除 null) | 作为 Object | 安全 |
List<? extends A> |
A 或 A 的子类 | 不能(除 null) | 作为 A | 安全 |
List<? super A> |
A 或 A 的父类 | 能添加 A 及其子类 | 作为 Object | 安全 |
记忆技巧
"extends 只出不进,super 只进不出"
? extends T:生产者,只能从中读取(产出 T)- 不能添加(除 null)
- 读只能按T来读。因为T和T的子类都是T
? super T:消费者,只能向其添加(消费 T)- 下界通配符 (
? super T) 的添加规则:- 可以添加:T 和 T 的子类
- 不能添加:T 的父类或无关类型
- 读只能按Object来读。因为T和T的父类都是Object
- 下界通配符 (
实际使用建议
- 需要读取 → 用
? extends T - 需要写入 → 用
? super T - 既读又写 → 不要用通配符,用具体类型
<T> - 完全通用,不关心类型 → 用
?
示例说明
import java.util.ArrayList;
import java.util.List;
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
public class Test {
// 生产者 - 只能读
public static void processAnimals(List<? extends Animal> animals) {
for (Animal a : animals) { // OK - 读取
System.out.println(a);
}
// animals.add(new Dog()); // 编译错误!
// animals.add(new Cat()); // 编译错误!
// 编译器不知道 list 的实际类型是什么!可能是Animal及其子类,因此可能是 List<Animal>、List<Dog> 或 List<Cat>
// 如果animals是List<Animal>那没问题,可以加Dog和Cat
// 如果animals是List<Dog>,那就不能加animals.add(new Cat());
// 如果animals是List<Cat>,那就不能加animals.add(new Dog());
// 底层逻辑的核心:编译器在进行类型检查时,必须保守地假设最坏情况。animals不是List<Animal>,就出现问题了。
// 对于<? extends T>,它不知道具体的类型参数是什么,为了保证绝对的类型安全,它必须禁止任何可能破坏类型安全的操作。
// 这种保守策略确保了"一次编写,到处运行"的类型安全性。
}
// 消费者 - 只能写
public static void addDogs(List<? super Dog> dogs) {
// 无论实际是什么列表,Dog 都能安全放入
dogs.add(new Dog());
// Dog → Dog(如果 list 是 List<Dog>)
// Dog → Animal(如果 list 是 List<Animal>)
// Dog → Object(如果 list 是 List<Object>)
// 编译器不知道 list 的实际类型是什么!可能是 List<Dog>、List<Dog的父类>
// 这里Dog的父类有Animal,object,因此可能是 List<Dog>、List<Animal>、List<Object>
// 无论实际是什么列表,Dog 都能安全放入。但是Animal、Object不能安全放入
// 因为可能是List<Dog>、List<Animal>、List<Object>
// 取出来只能作为 Object取出,因为Dog、Animal、Object都是Object
Object object = dogs.get(0); // 编译成功!只能作为 Object
// Dog d = dogs.get(0); // 编译错误!只能作为 Object
}
public static void main(String[] args) {
List<Dog> dogList = new ArrayList<>();
processAnimals(dogList); // OK
List<Animal> list = new ArrayList<>();
list.add(new Dog());
list.add(new Cat());
list.add(new Animal());
processAnimals(list); // OK
List<Animal> animalList = new ArrayList<>();
addDogs(animalList); // OK - Animal 是 Dog 的父类
}
}
这种设计确保了类型安全,虽然有些限制,但防止了运行时出现 ClassCastException。
浙公网安备 33010602011771号