Collection接口
Collection 接口源码分析
Collection 是集合框架两大根系之一(另一支是 Map),List、Set、Queue 全部从它长出来。
它本身一行算法都没有:全是方法声明加几个 default 方法,价值在于「契约」——规定一个元素容器必须回答哪些问题(多大、有没有、给我遍历器)、允许拒绝哪些操作。
读懂这份契约,ArrayList、HashSet 那些实现类的方法表就不用背了,它们只是在各自的数据结构上兑现同一份合同。
代码块收起展开
// 基于本地 JDK 源码 (D:/1ForCode/JAVA_Source, java.base, 2024 版), java.util.Collection
public interface Collection<E> extends Iterable<E> {
// Query Operations
int size();
boolean isEmpty();
boolean contains(Object o); // 判定完全由 equals 定义:o==null ? e==null : o.equals(e)
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
default <T> T[] toArray(IntFunction<T[]> generator) { // JDK 11+:coll.toArray(String[]::new) 的落点
return toArray(generator.apply(0));
}
// Modification Operations
boolean add(E e); // 集合被改变才返回 true:Set 拒收重复元素时返回 false
boolean remove(Object o);
// Bulk Operations
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
default boolean removeIf(Predicate<? super E> filter) { // JDK 8+:官方版"边遍历边删"
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove(); // 走迭代器自己的 remove 才不会触发 fail-fast
removed = true;
}
}
return removed;
}
boolean retainAll(Collection<?> c);
void clear();
// Comparison and hashing
boolean equals(Object o);
int hashCode();
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
default Stream<E> stream() { // 所有集合接入 Stream 流水线的总入口
return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
}原理串讲
先看它在族谱里的位置。Collection extends Iterable,而 Iterable 只有一个必须实现的方法 iterator()——这一层继承换来的是语言级特权:任何 Collection 都能直接上 for-each 循环,编译器会自动翻译成迭代器写法。
往下走,List(有序可重复)、Set(不重复)、Queue(队头操作)三大子接口各自收窄语义,但方法主体都是从这里继承的。
Map 家族刻意不在这棵树上:键值对不是”一组元素”,硬套 Collection 的单元素方法会很拧巴,所以 Map 通过 keySet()、values()、entrySet() 三个视图把自己”投影”回 Collection 世界。
第二个关键设计是「可选操作」(optional operation)。add、remove、clear 这些修改方法在 javadoc 里都标着 optional,实现类可以直接抛 UnsupportedOperationException 拒绝执行——List.of(...) 和 Collections.unmodifiableList(...) 返回的不可变集合就是这么干的。
为什么不拆成”只读接口”和”可写接口”两层?因为可读/可写/定长/元素受限这些维度组合起来接口数量会爆炸,JDK 设计者选了胖接口加运行时拒绝的路线,代价是把类型错误从编译期推迟到了运行期:一个 Collection 引用能不能 add,签名上看不出来,只有跑起来才知道。
第三,整个接口的相等语义都压在 Object.equals 上。contains、remove 的规范原文写的就是”存在元素 e 使得 o<mark>null ? e</mark>null : o.equals(e)”。
这正是”自定义类进集合必须同时重写 equals 和 hashCode”这条规矩的合同源头——契约在顶层接口里就写死了判定方式,哈希类实现只是在 equals 之前多用 hashCode 做了一层加速预筛。
最后是 default 方法的演进史。
JDK 8 之前接口加方法等于打破所有实现类,集合框架因此僵化多年;
接口能带默认实现之后,Collection 直接长出 removeIf、stream、parallelStream、spliterator,JDK 11 又补了 toArray(generator)——老实现类一行不改就白得新能力,新实现类想优化再覆写(比如 ArrayList 的 removeIf 用位图批量删,比默认的逐个 remove 快)。
配套的 AbstractCollection 骨架类把 contains、toArray、toString 这些通用逻辑全垫好,子类只需实现 iterator() 和 size() 就能得到一个能用的只读集合。
设计取舍
- 胖接口 + 可选操作,而不是拆细接口:省下接口组合爆炸,代价是
UnsupportedOperationException只能运行期暴露。 - 查询方法收
Object而不是E:contains(Object)、remove(Object)是刻意的——查和删不需要类型安全(放得进去才需要),这让”拿一个Collection<String>查Integer”能编译通过、永远返回 false,编译器不拦。 toArray(T[] a)是泛型数组不能 new 的历史补丁,传小了重新分配、传大了尾部填 null;JDK 11 的toArray(IntFunction)用方法引用把这套仪式简化成String[]::new。- null 元素契约层面留白:接口不禁止 null,ArrayList 收、ArrayDeque 和 ConcurrentHashMap 拒——用哪个实现前先想清楚 null 会不会混进来。
- 契约还在生长:JDK 21 在 Collection 和 List 之间插了
SequencedCollection(统一 first/last/reversed 语义),二十多年的老树至今仍在抽新枝。