Collection接口

Collection 接口源码分析

Collection 是集合框架两大根系之一(另一支是 Map),List、Set、Queue 全部从它长出来。
它本身一行算法都没有:全是方法声明加几个 default 方法,价值在于「契约」——规定一个元素容器必须回答哪些问题(多大、有没有、给我遍历器)、允许拒绝哪些操作。
读懂这份契约,ArrayList、HashSet 那些实现类的方法表就不用背了,它们只是在各自的数据结构上兑现同一份合同。

代码块JAVA · 60 行收起展开
// 基于本地 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);
    }
}

原理串讲

Java 集合框架继承族谱

先看它在族谱里的位置。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 上。containsremove 的规范原文写的就是”存在元素 e 使得 o<mark>null ? e</mark>null : o.equals(e)”。
这正是”自定义类进集合必须同时重写 equals 和 hashCode”这条规矩的合同源头——契约在顶层接口里就写死了判定方式,哈希类实现只是在 equals 之前多用 hashCode 做了一层加速预筛。

最后是 default 方法的演进史。
JDK 8 之前接口加方法等于打破所有实现类,集合框架因此僵化多年;
接口能带默认实现之后,Collection 直接长出 removeIfstreamparallelStreamspliterator,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 语义),二十多年的老树至今仍在抽新枝。

延伸阅读