list 是有序、可重复、允许 null 的集合,底层常用数组或链表实现,支持按索引访问和动态扩容。

为什么 List extends T> 不能 add 元素?
因为编译器只知道它“是 T 或它的子类”,但不知道具体是哪一个——可能是 Integer,也可能是 Double,往一个 List extends Number> 里 add(new Integer(1)),万一它实际是 List<double></double>,就会破坏类型安全。所以 Java 直接禁止非 null 的写入。
- ✅ 安全读取:
Number n = list.get(0)—— 所有子类都可向上转型为Number - ❌ 禁止写入:
list.add(new Integer(1))编译报错:“cannot resolve method add” - ⚠️ 唯一允许的写入是
null,但它对业务无意义,且掩盖设计问题
什么时候该用 List super T>?
当你需要把 T 类型(或其子类)的对象“塞进去”,而不在乎容器原本多宽泛——比如工具方法接收任意能装下 Integer 的集合:可能是 List<integer></integer>、List<number></number>,甚至 List<object></object>。
- ✅ 安全写入:
list.add(new Integer(42))、list.add(new Long(100))都合法(只要Long是Integer的子类?不,这里注意:不是!但Integer是Number子类,所以List super Number>才能收Integer;关键看T和通配符的关系) - ❌ 安全读取受限:
Object o = list.get(0)可以,但Integer i = list.get(0)编译失败——你只保证容器“能装T”,不保证“只装T” - 典型场景:
Collections.copy(dest, src)中dest参数就是List super T>,确保能写入src里的每个T
PECS 原则怎么落地到方法签名?
Producer Extends, Consumer Super —— 不是口号,是参数设计铁律。方法参数若从集合取数据(生产者),用 ? extends T;若往集合放数据(消费者),用 ? super T。
- ❌ 错误示例:
void process(List extends Runnable> tasks)却在里面调用tasks.add(...)→ 编译不过 - ✅ 正确拆分:
void copy(List extends String> src, List super String> dest)→ 从src读,往dest写,各司其职 - ⚠️ 注意:返回值类型一般不用通配符(如
List extends T> method()虽合法,但调用方难处理;优先返回具体泛型如List<t></t>或List extends T>仅当必要)
常见误用:把 ? extends / ? super 当作“泛型缩写”来声明变量
你不能用通配符实例化对象,也不能把它当类型反复赋值——它只适合方法参数和临时读写约束。
- ❌ 错误:
List extends Number> list = new ArrayList extends Number>();→ 编译错误:“cannot instantiate wildcard type” - ❌ 危险:
List super Integer> l1 = new ArrayList<number>(); List super Integer> l2 = l1;</number>看似合理,但后续l2.add(new Object())合法却语义错误(Object不是Integer的子类,但它是Integer的父类?不,Object是父类,但? super Integer允许Object,而add(new Object())是允许的——这恰恰暴露风险:你本想只塞Integer,结果因类型太宽泛,误塞了Object,破坏下游假设 - ✅ 安全做法:变量声明用具体类型(如
List<number></number>),仅在方法参数中引入通配符做兼容性适配
? super 返回 Object 时,类型信息就真丢了。










