2

编辑问题以删除我的类比并直接询问。JDKHashSet实现如下:

public class HashSet {
  private HashMap map;
  public HashSet(int capacity) {
    map = new HashMap(capacity);
  }

  public HashSet(int capacity, float loadFactor) {
    map = new HashMap(capacity, loadFactor);
  }

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

并且LinkedHashSet是这样实现的:

public class LinkedHashSet
  extends HashSet {
  public LinkedHashSet(int capacity) {
    super(capacity, 0, true);
  }
  public LinkedHashSet(int capacity, float loadFactor) {
    super(capacity, loadFactor, true);
  }
}

class 中的第三个构造函数HashSetHashSet(int capacity, loadFactor, boolean dummy)仅用于帮助LinkedHashSetclass 使用 aLinkedHashMap作为支持映射而不是默认值HashMap

问题:

  • 这会被认为是一个好的设计吗?
  • 让子类指定支持map类型不是更好吗?
  • 我花了 30 分钟在 JDK 源代码中找到双向链表的确切位置,LinkedHashSet因为我没有直观地想到上述实现范式,这样的设计什么时候会是一个好的选择?
4

1 回答 1

2

你是对的,这不是最好的设计选择。

总结一下:

HashSet 有 3 个构造函数:

/**
 * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
 * the specified initial capacity and the specified load factor.
 *
 * @param      initialCapacity   the initial capacity of the hash map
 * @param      loadFactor        the load factor of the hash map
 * @throws     IllegalArgumentException if the initial capacity is less
 *             than zero, or if the load factor is nonpositive
 */
public HashSet(int initialCapacity, float loadFactor) {
    map = new HashMap<>(initialCapacity, loadFactor);
}

/**
 * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
 * the specified initial capacity and default load factor (0.75).
 *
 * @param      initialCapacity   the initial capacity of the hash table
 * @throws     IllegalArgumentException if the initial capacity is less
 *             than zero
 */
public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}

/**
 * Constructs a new, empty linked hash set.  (This package private
 * constructor is only used by LinkedHashSet.) The backing
 * HashMap instance is a LinkedHashMap with the specified initial
 * capacity and the specified load factor.
 *
 * @param      initialCapacity   the initial capacity of the hash map
 * @param      loadFactor        the load factor of the hash map
 * @param      dummy             ignored (distinguishes this
 *             constructor from other int, float constructor.)
 * @throws     IllegalArgumentException if the initial capacity is less
 *             than zero, or if the load factor is nonpositive
 */
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

最后一个有一个额外的参数 dummy,它没有被使用,只是为了让编译器区分具有相同参数的 2 个构造函数。唯一的区别是更改支持映射实现。

自从编写了这些类以来,我们在对象设计方面已经取得了很大进步。

如果今天重写,可能会有一个构造函数Map取而代之,以便任何 Map 实现都可以用作后备存储:

HashSet(Map<K,V> backingMap);

和/或可能有多个名称不同但参数相同的静态工厂方法

 public static HashSet create(int initialCapacity, float loadFactor)

 public static HashSet createLinked(int initialCapacity, float loadFactor)

编辑

JB Nizet 提出了一个有趣的观点。HashSet 的readObject()方法在从 ObjectInputStream 重构对象时具有显式代码,以查看“this”实例是否属于 LinkedHashSet。

// Create backing HashMap
    map = (((HashSet<?>)this) instanceof LinkedHashSet ?
           new LinkedHashMap<E,Object>(capacity, loadFactor) :
           new HashMap<E,Object>(capacity, loadFactor));

这只是真正可能的,因为构造函数的虚拟参数版本是包私有的,所以这是目前仅有的两种可能的实现。如果没有这种技术,您将无法正确使用 ReadObject()。

这可能是 Josh Bloch 在 Effective Java 中编写序列化章节的原因。您可能必须使用 SerializationProxy (Item 78) 才能正确读取 LinkedHashSet。

于 2015-03-27T20:22:47.683 回答