1. List<? extends Dog> 和 add 操作
首先来看 List<? extends Dog> 的情况,它使用了 上限通配符 extends。由于你使用了 extends Dog,它表示列表的元素类型可能是 Dog 或者 Dog 的某个子类。虽然你能读取列表中的元素,但 你不能安全地添加任何类型的元素,因为你无法确定该列表实际接受哪种具体类型。换句话说,List<? extends Dog> 是一个“只读”的列表,只能从中读取 Dog 或它的子类,但不能随意向其中添加元素。
List<? extends Dog> list = new ArrayList<>();
list.add(new Dog()); // 编译错误:无法添加元素
list.add(new Poodle()); // 编译错误:无法添加元素
Objec object = list.get(0) // 读取元素是合法的
Dog dog = list.get(0); // 读取元素是合法的,但返回类型为 `Object`,这里强制转换
为什么不能添加元素?
- 你无法知道
List<? extends Dog>中到底存储的是Dog还是它的某个子类(例如Poodle)。 - 如果你能够添加元素,那么假设你添加了一个
Cat对象(假设Cat是Dog的兄弟类),编译器无法确保类型的安全性。
总结: 在 List<? extends Dog> 中,你无法安全地添加任何元素,因为它可能存储的是 Dog 的子类,无法保证添加的元素类型与列表中的元素兼容。
2. List<? super Dog> 和 add 操作
接下来看 List<? super Dog> 的情况,它使用了 下限通配符 super。这里,? super Dog 意味着该列表的元素类型可以是 Dog 或者 Dog 的父类(如 Animal 或 Object)。对于 add 操作,它是安全的,因为你明确知道你可以添加 Dog 类型及其子类(不是父类,而是子类)(例如 Poodle)。
List<? super Dog> list = new ArrayList<>();
list.add(new Dog()); // 可以安全地添加
list.add(new Poodle()); // 可以安全地添加
list.add(new Animal()); // 错误,只能添加Dog以及子类
Objec object = list.get(0) // 读取元素是合法的
Dog dog = list.get(0); // 读取元素是合法的,但返回类型为 `Object`,这里强制转换
为什么可以添加元素?
- 你可以向
List<? super Dog>中添加Dog或其子类的元素,因为无论它的实际类型是什么(Dog、Animal、Object),你都可以安全地假定Dog或其子类是符合要求的。 - 这里的关键是
super Dog指定了下限,意味着列表接受Dog及其任何子类作为元素,因此可以添加这些类型。
3. 区别总结:
| 操作 | List<? extends Dog> | List<? super Dog> |
|---|---|---|
| 读取(get) | 可以读取,类型是 Dog 或其子类,但会返回 Object | 可以读取,但类型不确定,所以也需要当做 Object 类型处理 |
| 添加(add) | 不能添加任何元素,因为你不能确定具体类型 | 可以添加 Dog 或其子类的元素,但不能添加超出 Dog 的类型 |
| 安全性 | 安全地读取,但不能安全地添加元素 | 可以安全地添加 Dog 或其子类的元素,但读取时会丧失类型信息 |
4.如何添加元素?
不能通过add,不过可以通过换引用方式赋值,例如:
List<Integer> intList = new ArrayList<>(Arrays.asList(1, 2, 3)); // 原始List有元素
List<? extends Number> numbers = intList; // 通过通配符引用
System.out.println(numbers.get(0)); // 输出1 ✅
List<Object> objList = new ArrayList<>(Arrays.asList("A", "B")); // 原始List有元素
List<? super Number> sink = objList; // 通配符引用
sink.add(3.14); // ✅ 可以写入Number的子类
System.out.println(objList); // 输出 [A, B, 3.14]

284

被折叠的 条评论
为什么被折叠?



