2012-10-28 14 views
8

クラスがあり、このクラスのこのインスタンスを格納できるHashSetを作成したとします。等しいインスタンスを追加しようとすると、コレクション内に1つのインスタンスしか保持されず、それは問題ありません。含まれている要素が変更された場合、Java HashSetに重複が含まれます

しかし、HashSetに2つの異なるインスタンスがあり、それを1つ取ってそれを他のものと完全にコピーすると(フィールドをコピーすることによって)、HashSetは2つの重複インスタンスを含みます。ここで

はこのことを示すコードです:

public static void main(String[] args) 
    { 
     HashSet<GraphEdge> set = new HashSet<>(); 
     GraphEdge edge1 = new GraphEdge(1, "a"); 
     GraphEdge edge2 = new GraphEdge(2, "b"); 
     GraphEdge edge3 = new GraphEdge(3, "c"); 

     set.add(edge1); 
     set.add(edge2); 
     set.add(edge3); 

     edge2.setId(1); 
     edge2.setName("a"); 

     for(GraphEdge edge: set) 
     { 
      System.out.println(edge.toString()); 
     } 

     if(edge2.equals(edge1)) 
     { 
      System.out.println("Equals"); 
     } 
     else 
     { 
      System.out.println("Not Equals"); 
     } 
    } 

    public class GraphEdge 
    { 
     private int id; 
     private String name; 

     //Constructor ... 

     //Getters & Setters... 

     public int hashCode() 
     { 
     int hash = 7; 
     hash = 47 * hash + this.id; 
     hash = 47 * hash + Objects.hashCode(this.name); 
     return hash;  
     } 

     public boolean equals(Object o) 
     { 
      if(o == this) 
      { 
       return true; 
      } 

      if(o instanceof GraphEdge) 
      { 
       GraphEdge anotherGraphEdge = (GraphEdge) o; 
       if(anotherGraphEdge.getId() == this.id && anotherGraphEdge.getName().equals(this.name)) 
       { 
        return true; 
       } 
      } 

       return false; 
     } 
    } 

上記のコードの出力:

1 a 
1 a 
3 c 
Equals 

は、その内容となるよう可能な重複したエントリを検証するためにHashSetのを強制する方法はあります上記のように作成されたシナリオは削除されますか?

新しいハッシュセットを作成してその内容をあるハッシュセットから別のハッシュセットにコピーして、新しいハッシュセットに重複が含まれないようにすることができます。

答えて

16

あなたが記述状況が無効です。 Javadocを参照してください: "オブジェクトがセット内の要素である間に等価比較に影響するようにオブジェクトの値が変更された場合、セットの動作は指定されません。"

+0

そう、上記のシナリオは無効です。私は唯一のオプションは、新しいHashSetに内容をコピーすることだと思います。 –

+4

@ Spi1988正しい解決策は、 'Set'の契約に固執し、オブジェクトをコレクションに追加した後にオブジェクトを変更しないことです。 – EJP

+0

@PB_MLT内容を新しいHashSetにコピーすることで、あなたは何を達成しますか? – HungryForKnowledge

-1

Objects.hashCodeは、パラメータオブジェクトを使用してコードを生成するために使用されます。あなたはhascode計算の一部としてそれを使用しています。

は、以下でのhashCodeの実装を交換してみてください:

public int hashCode() 
{ 
    return Objects.hashCode(this.id, this.name); 
} 
+0

hashCodeメソッドが1つのオブジェクトしか取らないため、Objects.hashCode(this.id、this.name)は無効です。 –

+0

私はあなたがGoogle Collectionsライブラリを使用していると仮定しました: –

+0

http://google-collections.googlecode.com/svn/trunk/javadoc/com/google/common/base/Objects.html#hashCode(java.lang.Object)。 ..) –

1

あなたは正しいです、私はあなたが議論するケースに対して保護する方法はないと思います。ハッシングと等価を使用するすべてのコレクションは、この問題の影響を受けます。コレクションには、オブジェクトがコレクションに追加されてから変更されたという通知はありません。あなたが描いている解決策は良いと思います。

この問題に関心がある場合は、おそらくデータ構造を再考する必要があります。たとえば、不変オブジェクトを使用することができます。不変オブジェクトの場合、この問題は発生しません。

1

HashSetは、オブジェクトが追加された後でメンバーのプロパティが変更されていることに気づきません。これが問題の場合は、GraphEdgeを変更できないようにすることをお勧めします。例えば:GraphEdgeが不変である場合には

GraphEdge edge4 = edge2.changeName("new_name"); 

、むしろ既存のインスタンスを変更する新しいインスタンスを返すの値の結果を変更します。

-1

リストを繰り返し処理するときに、独自の検出を行う必要があります。何が起こるかは、@ EJPの答えに追加するには

public class TestIterator { 
    public static void main(String[] args) { 
     List<String> list = new ArrayList<String>(); 

     list.add("1"); 
     list.add("1"); 
     list.add("2"); 
     list.add("3"); 

     for (String s : new UniqueIterator<String>(list)) { 
      System.out.println(s); 
     } 
    } 
} 

public class UniqueIterator<T> implements Iterable<T> { 
    private Set<T> hashSet = new HashSet<T>(); 

    public UniqueIterator(Iterable<T> iterable) { 
     for (T t : iterable) { 
      hashSet.add(t); 
     } 
    } 

    public Iterator<T> iterator() { 
     return hashSet.iterator(); 
    } 
} 
+0

彼はリストを持っていません。彼にはセットがある。彼はそれを悪用している。答えではありません。 – EJP

+0

彼はリストをセットとして使用しています。だから彼は適切にセットを使用するか、リストを使用する必要があります。 – slipperyseal

+0

彼はリストを望んでいません。彼はセットが欲しい。彼にはセットがある。彼はそれを悪用し、その要素がなぜユニークではないのか疑問に思っています。解決策はそれを悪化させることではなく、最初に起こることを止めることです。 – EJP

3

...新しいHashSetのは、行くための正しい方法は思えないかもしれません作るが、なぜで開始するHashSetのを使用して...これを試してみて、そうでないかもしれないではありません(equals/hashcode契約の意味で)それらを複製するようにHashSetのオブジェクトを変更すると、実際にはハッシュテーブルのデータ構造が壊れることになります。突然変異の正確な詳細に依存して、ハッシュテーブル、インスタンスの一方または両方の状態は、(例えば、containsと他の操作)を検索するために見えなくなるであろう

  • 。間違ったハッシュチェーン上にあるか、またはハッシュチェーンの前に他のインスタンスが表示されるためです。そして、どのインスタンスが表示されるのか、そしてそれが目に見えるかどうかを予測するのは難しいです。

  • セットを反復すると、両方のインスタンスがまだ存在します... Set契約に違反しています。

もちろん、これはアプリケーションの観点から非常に壊れています。


あなたがいずれかの方法でこの問題を回避することができます

  • をご集合要素のための不変の型を使用して、
  • あなたがセットに入れて、および/またはプルなどのオブジェクトのコピーを作成しますそれは「知っている」という期間のオブジェクトを変更しないように、セットのうち、それらは、
  • は...あなたのコードを書く

正確性と堅牢性の観点から、最初の選択肢は明らかに最高です。


なお、これを一般的な方法で「修正」することは本当に困難です。 Javaには、いくつかの要素が変更されたことを知り、通知されているという普遍的なメカニズムはありません。このようなメカニズムはクラス単位で実装できますが、明示的にコード化する必要があります(安価ではありません)。あなたがそのような仕組みを持っていたとしても、あなたは何をしますか?明らかに、オブジェクトの1つがセットから削除されるはずですが...どちらがどちらですか?

+0

Thx。セット内のオブジェクトが変更されたことを検出し、同じセット内に存在する別のオブジェクトと等しいことを検出できるメカニズムがあれば、重複のいずれかを削除することができます(削除したオブジェクト彼らは等しい)。 –

+0

@ Spi1988 - *「どちらが同じであるので削除してもかまいません」*。それは一般的には当てはまりません。 'equals()'が 'true'を返す2つのオブジェクトは、同一である必要はありません。そして、あなたがどれを落とすかは重要なことかもしれません。あなたが仮定しているメカニズムは仮説です。 –

+0

ありがとう、私はこれで何時間も苦労していました。しかし正直なところ、この問題は、実装がHashTableでバックアップするのではなく、作成時にhashCodeインデックスをフリーズさせるのではなく、適切なHashSetを作成するのが面倒だからです。私がこのHashSetを隠す限り、彼らは私たちにHashSetではないが、ImmutableHashSetと適切なHashSet実装はまだjdkから抜けています。これは実際にはおかしなことです。ワオ。 –

関連する問題