JDK 源码分析之 Set 类详解——适配器模式的应用

编者注:本文为历史博文归档;涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效,阅读时请注意时效性。

JDK 源码中的 Set 类是开发过程中经常使用的集合类型。本文将对 JDK 源码中 Set 类的构造进行深入分析,帮助开发者在编程中更高效地应用。

Set 类的核心特性与实现原理

在 JDK 源码中,Set 类最常用的特性是不允许包含重复元素。那么,判断元素是否相同是如何实现的呢?我们可以通过下面的类图来直观理解:

JDK 源码分析之 Set 类图

观察上述类图,我们可以发现一个经典设计模式的应用,那就是适配器模式(Adapter Pattern)。源码将 Map 接口的对象包装成为了 Set 接口。下面我们通过代码具体分析这一实现机制。

HashSet 源码分析

首先查看 HashSet 的核心成员变量:

private transient HashMap<E, Object> map;

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

可见,HashSet 内部适配了 HashMap。那么它的功能是如何委托给 HashMap 结构的呢?关键在于 add 方法的实现:

public boolean add(E e) {
    return map.put(e, PRESENT) == null;
}

HashMap 中,我们大多数时候关注的是 value,但是在 Set 的实现中,却很好地利用了已有类 HashMap 的特性。它利用了 HashMapkey 的唯一性来保证存储在 Set 中的元素的唯一性。

private static final Object PRESENT = new Object();

上述 PRESENT 对象是 HashMap 所有 key 对应的 value,它只是一个形式占位符,而我们真正的数据是存储在 key 中的资源。

此外,HashSet 拿到的迭代器也是基于 Map 的 keySet:

public Iterator<E> iterator() {
    return map.keySet().iterator();
}

即直接返回 MapkeySet 的迭代器。

LinkedHashSet 源码分析

同理,我们看看 LinkedHashSet 的实现。它提供了多个构造方法:

public LinkedHashSet(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor, true);
}

/**
 * Constructs a new, empty linked hash set with the specified initial
 * capacity and the default load factor (0.75).
 *
 * @param   initialCapacity   the initial capacity of the LinkedHashSet
 * @throws  IllegalArgumentException if the initial capacity is less
 *              than zero
 */
public LinkedHashSet(int initialCapacity) {
    super(initialCapacity, .75f, true);
}

/**
 * Constructs a new, empty linked hash set with the default initial
 * capacity (16) and load factor (0.75).
 */
public LinkedHashSet() {
    super(16, .75f, true);
}

这些构造方法均调用了父类(HashSet)的构造函数。父类中对应的构造函数如下:

HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

由此可见,LinkedHashSet 内部实例化了 LinkedHashMap,从而保持了元素的插入顺序。

TreeSet 源码分析

同理,我们亦可见到 TreeMap 的实现影子。在 TreeSet 中,底层存储结构为:

private transient NavigableMap<E, Object> m;

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

设计模式总结

综上所述,Set 接口的实现类内部均委托给了 Map 接口的实现类。虽然从某些角度理解,这也可以被视为桥接模式(Bridge Pattern)的一种变形,但从语义意义上分析,我更倾向于认为这是适配器模式的应用:将 Map 适配为 Set 接口供外部使用。

希望这篇对 JDK 源码中 Set 类的分析能对你有所帮助。


说明:本文基于早期 JDK 版本源码分析(参考图片上传时间为 2009 年)。虽然核心实现原理(基于 Map 实现 Set)在后续版本(如 JDK 1.8+)中依然保持兼容,但具体内部类名或方法细节可能随版本迭代有所调整,请以当前官方文档为准。