web-dev-qa-db-ja.com

同一性と一致するようにComparableを実装する方法

等しい(equals()ごとの)クラスmustがオブジェクトIDによって定義されているクラス、つまり_this == other_があります。

そのようなオブジェクトを並べるためにComparableを実装したいと思います(たとえば、いくつかのgetName()プロパティによって)。 equals()と一貫性を保つために、2つのオブジェクトが同じ名前であっても、compareTo()は_0_を返してはなりません。

compareToの意味でオブジェクトIDを比較する方法はありますか? System.identityHashCode(o)を比較することもできますが、ハッシュの衝突が発生した場合でも_0_が返されます。

18
Balz Guenat

ここでの本当の答えは、Comparableを実装しないことです。このインターフェースの実装は、オブジェクトにnaturalの順序があることを意味します。 「等しい」ものは、​​その考えをフォローアップするときに同じ場所にある必要があります。

仮にカスタムコンパレータを使用する必要がある場合でも、それでもあまり意味がありません。 a <b ...を定義するものがa == bを与えることが許可されていない場合(aとbが<関係に従って「等しい」場合)、の全体的な比較ユースケースで壊れています。

言い換えれば、「どういうわけか」結果があなたが望むものになるクラスにコードを入れることができるからといって...それをgoodのアイデアにするわけではありません。

定義により、各オブジェクトにアイデンティティプロパティとして niversally unique identifier (UUID)(またはGlobally unique identifier、(GUID))を割り当てることにより、UUIDは同等であり、等しいと一致します。 Javaにはすでに [〜#〜] uuid [〜#〜] クラスがあり、生成されると、永続化のために文字列表現を使用できます。専用プロパティすべてのバージョン/スレッド/マシンでIDが安定していることも保証されます。すべてが一意のIDを取得するようにする方法がある場合は、増分IDを使用することもできますが、標準のUUID実装を使用すると、セットのマージによる問題から保護されます。同時にデータを生成する並列システム。

比較のために他のものを使用する場合、それはそのアイデンティティ/値とは別の方法で比較可能であることを意味します。したがって、このオブジェクトの比較可能な手段を定義し、それを文書化する必要があります。たとえば、人は名前、DOB、身長、または優先順位の組み合わせで比較できます。最も自然なのは、2人が同じ人物である場合とは別の規則(人間が簡単に検索できるようにするため)としての名前です。また、comparetoとequalsは異なるものに基づいているため、互いに素であることを受け入れる必要があります。

6
Tezra

クラスの各インスタンスに対して一意になる2番目のプロパティ(たとえば、int idまたはlong id)を追加できます(staticカウンター変数を使用し、それを使用してコンストラクターでidを初期化できます)。

次に、compareToメソッドで最初に名前を比較し、名前が等しい場合はidsを比較します。

インスタンスごとに異なるidがあるため、compareTo0を返すことはありません。

5
Eran

安定した一貫した比較/等価性の設定にはUUIDプロパティを使用する必要があるという私の元の答えを忠実に守っていますが、「本当に妄想的であり、比較可能な一意のIDを保証します。」.

基本的に、要するに、UUIDの一意性またはIDの一意性を信頼しない場合は、神があなたに対して積極的に陰謀を企てていることを証明するために必要なだけのUUIDを使用してください。 (例外をスローしないことが技術的に保証されているわけではありませんが、2つのUUIDを必要とすることは、どの正気の宇宙でもやりすぎになるはずです。)

import Java.time.Instant;
import Java.util.ArrayList;
import Java.util.UUID;

public class Test implements Comparable<Test>{

    private final UUID antiCollisionProp = UUID.randomUUID();
    private final ArrayList<UUID> antiuniverseProp = new ArrayList<UUID>();

    private UUID getParanoiaLevelId(int i) {
        while(antiuniverseProp.size() < i) {
            antiuniverseProp.add(UUID.randomUUID());
        }

        return antiuniverseProp.get(i);
    }

    @Override
    public int compareTo(Test o) {
        if(this == o)
            return 0;

        int temp = System.identityHashCode(this) - System.identityHashCode(o);
        if(temp != 0)
            return temp;

        //If the universe hates you
        temp = this.antiCollisionProp.compareTo(o.antiCollisionProp);
        if(temp != 0)
            return temp;

        //If the universe is activly out to get you
        temp = System.identityHashCode(this.antiCollisionProp) - System.identityHashCode(o.antiCollisionProp);;
        if(temp != 0)
            return temp;

        for(int i = 0; i < Integer.MAX_VALUE; i++) {
            UUID id1 = this.getParanoiaLevelId(i);
            UUID id2 = o.getParanoiaLevelId(i);
            temp = id1.compareTo(id2);
            if(temp != 0)
                return temp;

            temp = System.identityHashCode(id1) - System.identityHashCode(id2);;
            if(temp != 0)
                return temp;
        }

        // If you reach this point, I have no idea what you did to deserve this
        throw new IllegalStateException("RAGNAROK HAS COME! THE MIDGARD SERPENT AWAKENS!");
    }

}
1
Tezra

同じ名前の2つのオブジェクトがあると仮定して、equals()falseを返す場合、compareTo()は0を返さないはずです。

  • hashcode()をオーバーライドし、nameだけに依存しないようにしてください
  • 次のようにcompareTo()を実装します。
public void compareTo(MyObject object) {
    this.equals(object) ? this.hashcode() - object.hashcode() : this.getName().compareTo(object.getName());
}
0
Darshan Mehta

あなたはユニークなオブジェクトを持っていますが、エランが言ったように、衝突のために追加のカウンター/リハッシュコードが必要になるかもしれません。

private static Set<Pair<C, C> collisions = ...;

@Override
public boolean equals(C other) {
    return this == other;
}

@Override
public int compareTo(C other) {
    ...
    if (this == other) {
        return 0
    }
    if (super.equals(other)) {
        // Some stable order would be fine:
        // return either -1 or 1
        if (collisions.contains(new Pair(other, this)) {
            return 1;
        } else if (!collisions.contains(new Pair(this, other)) {
            collisions.add(new Par(this, other));
        }
        return 1;
    }
    ...
}

エランの答えをそのまま使用するか、要件を入力してくださいに問題があります。

  • 同一でない0比較のオーバーヘッドは無視できると考えるかもしれません。
  • ある時点でインスタンスが作成されなくなった場合、理想的なハッシュ関数を調べるかもしれません。これは、すべてのインスタンスのコレクションがあることを意味します。
0
Joop Eggen