9

クラスがあり、このクラスのこのインスタンスを格納できる HashSet を作成するとします。等しいインスタンスを追加しようとすると、コレクションに保持されるインスタンスは 1 つだけで問題ありません。

ただし、HashSet に 2 つの異なるインスタンスがあり、一方を (フィールドをコピーして) もう一方の正確なコピーにすると、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 にその内容を強制的に検証させる方法はありますか?

考えられる解決策は、新しいハッシュセットを作成し、あるハッシュセットから別のハッシュセットにコンテンツをコピーして、新しいハッシュセットに重複が含まれないようにすることですが、私はこの解決策が好きではありません。

4

6 に答える 6

18

あなたが説明した状況は無効です。Javadocを参照してください。

于 2012-10-28T23:29:53.693 に答える
3

@EJPの答えに追加すると、オブジェクトを変更しHashSetて重複させると(equals/hashcodeコントラクトの意味で)実際に起こることは、ハッシュテーブルのデータ構造が壊れることです。

  • 変更の正確な詳細とハッシュ テーブルの状態に応じて、インスタンスの 1 つまたは両方が参照できなくなります (例:containsおよびその他の操作)。間違ったハッシュ チェーン上にあるか、ハッシュ チェーン上で他のインスタンスがその前に表示されているためです。そして、どのインスタンスが表示されるかを予測するのは困難です...そして、それが表示されたままになるかどうか.

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

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


この問題は、次のいずれかで回避できます。

  • セット要素に不変型を使用し、
  • オブジェクトをセットに入れたり、セットから取り出したりするときに、オブジェクトのコピーを作成します。
  • 期間中オブジェクトを変更しないように「認識」するようにコードを記述します...

正確性と堅牢性の観点からは、最初のオプションが明らかに最適です。


ちなみに、これを一般的な方法で「修正」することは非常に困難です。Java には、何らかの要素が変更されたことを認識したり、通知を受け取ったりするための広範なメカニズムはありません。このようなメカニズムはクラスごとに実装できますが、明示的にコーディングする必要があります (コストもかかります)。もしそのような仕組みがあったとしても、あなたはどうしますか?明らかに、オブジェクトの 1 つをセットから削除する必要があります...しかし、どのオブジェクトでしょうか?

于 2012-10-29T00:09:35.023 に答える
1

あなたは正しく、あなたが話し合っているケースから保護する方法はないと思います. ハッシュと equals を使用するすべてのコレクションがこの問題の影響を受けます。コレクションには、オブジェクトがコレクションに追加されてから変更されたという通知はありません。あなたが概説した解決策は良いと思います。

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

于 2012-10-28T23:35:08.760 に答える
1

HashSetオブジェクトが追加された後に変更されたメンバーのプロパティを認識しません。これが問題になる場合は、GraphEdge不変にすることを検討してください。例えば:

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

が不変の場合GraphEdge、値を変更すると、既存のインスタンスが変更されるのではなく、新しいインスタンスが返されます。

于 2012-10-28T23:35:17.903 に答える
-1

リストを反復するときに一意の検出を行う必要があります。新しい HashSet を作成するのは正しい方法ではないように思えるかもしれませんが、これを試してみませんか...そして、最初から HashSet を使用しないでください...

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();
    }
}
于 2012-10-28T23:45:46.187 に答える
-1

Objects.hashCode は、パラメーター オブジェクトを使用して hascode を生成するために使用することを意図しています。hascode 計算の一部として使用しています。

hashCode の実装を次のものに置き換えてみてください。

public int hashCode()
{
    return Objects.hashCode(this.id, this.name);
}
于 2012-10-28T23:26:38.893 に答える