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);
  }
}

HashSetクラス:の 3 番目のコンストラクターは、クラスがデフォルトの代わりにバッキング マップとしてa を使用できるようHashSet(int capacity, loadFactor, boolean dummy)にするためだけに存在します。LinkedHashSetLinkedHashMapHashMap

質問:

  • これは良いデザインと見なされますか?
  • サブクラスにバッキングmapタイプを指定させたほうがよいのではないでしょうか?
  • LinkedHashSet上記の実装パラダイムが直感的に思い浮かばなかったため、JDK ソース コードで二重リンク リストが正確にどこにあるかを特定するのに 30 分かかりました。
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);
}

最後のものには、使用されない追加のパラメーター ダミーがあり、コンパイラーが同じパラメーターを持つ 2 つのコンストラクターを区別できるようにするためだけに存在します。唯一の違いは、バッキング マップの実装を変更することです。

これらのクラスが作成されて以来、私たちはオブジェクトの設計を大幅に改善しました。

これが今日書き直された場合、MapMap 実装をバッキング ストアとして使用できるように、代わりに を使用するコンストラクターがおそらく存在するでしょう。

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 からオブジェクトを再構築して「これ」のインスタンスが LinkedHashSet のものかどうかを確認する際の明示的なコードがあります。

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

これは、コンストラクターのダミー パラメーター バージョンがパッケージ プライベートであるため、実際にのみ可能であり、現在可能な実装は 2 つだけです。この手法がないと、ReadObject() を正しく使用できません。

これがおそらく、Josh Bloch が『Effective Java』のシリアライゼーションの章を書いた理由です。LinkedHashSet を正しく読み取るには、おそらく SerializationProxy (項目 78) を使用する必要があります。

于 2015-03-27T20:22:47.683 に答える