2016-10-06 7 views
2

私は、JavaのCollectionsの契約に準拠するために(予期しない動作を避けるために)、Comparatorはequalsと一貫している必要があることを知っています。コンパレータと等価と一致:どのように?

私はこのような者があるとします。

class Person { 
    private String name; 
    private String surname; 
    private int age; 


    public Person(String name, String surname, int age) { 
    this.name = name; 
    this.surname = surname; 
    this.age = age; 
    } 

    public String getName() { return name; } 
    public String getSurname() { return surname; } 
    public int getAge() { return age; } 
} 

コンパレータは、そのComparatorを使用して、

class PersonComparator implements Comparator<Person> { 

    @Override 
    public int compareTo(Person p1, Person p2) { 
    // throws NPE's 
    return p1.getName().compareTo(p2.getName()); 
    } 
} 

今、私はいくつかのソートされたコレクション(TreeMapSortedSet、...何を)したいだろうPersonの名前のみを比較します。 このコンパレータは、 "equal with equals"契約に違反します。しかし、私はequals(Object o)を無効にしたくありません。プログラムの他の部分では、同じ名前、姓、年齢の2人の人が(実際の人と同じように)異なる可能性があるからです。

選択したコレクションに名前がPersonを一意に識別したい、つまりTreeSet<Person>に「John Doe」と「John Smith」(同じ名前)を使用できないようにします。

これは、私がそれらをテストした限り、Java Collectionsの現在の実装でうまくいきます。

私の質問は:どのように契約を壊すことなく理想的にこれを「正しく」行うのですか?可能であれば、私は第三者図書館を避けたいと思います。もちろん、契約を解消するためにデータ構造を自分で実装したくありません。 私の懸念は、契約に違反するため、将来のJavaリリースでコードが壊れる可能性があることです。

+2

「Comparator」は、equalsと一致する必要はありません。それは一般的ですが、ではなくが厳密に必要です。 ((x、y)== 0と比較)==(x.equals(y))。一般的に言えば、 この条件に違反するコンパレータは、この事実を明確に示すはずです( )。推奨される言語は "Note:この比較器 はequalsと矛盾する順序を課します。" – Eran

+3

Personクラスのすべてのデータが人を明確に識別できない場合は、モデルが不十分である可能性があります。最終的には、_same_人を表すための複数の 'Person'インスタンスがある状況に遭遇します。だから私はいくつかの一意の識別子を追加して、あなたのコンパレータと同様に 'equals()'でそれを使うことを提案したいと思います。 – Thomas

+0

@Thomas「モデルが不十分であるかもしれないことを示している」 - ありがとう、私はそれが良い点だと思う。 (私の実際のユースケースはかなり異なっていますが、その点はまだ当てはまるかもしれません...) – Moritz

答えて

2

問題は、重複を無視することです。あなたが2つのPersonが同じであると言うなら、それは重複としてそれらを見て、それらの1つを落とします。

名前に「John」という名前の2人の人がいる場合、それらは一度TreeSetに表示されます。

あなたが必要とするのは、等価で同じフィールドを比較することです。

int cmp = p1.getName().compareTo(p2.getName()); 
if (cmp == 0) 
    cmp = p1.getSurname().compareTo(p2.getSurname()); 
if (cmp == 0) 
    cmp = Integer.compare(p1.getAge() - p2.getAge()); 
return cmp; 

このようにすると、equals == trueの2人だけが重複しているとみなされます。

同じ内容を持つ(ただし異なる)人々が重複して扱われることを防ぐ方法は、カウンタまたはUUIDの一意のIDを追加することです。

注:契約は一貫している必要はなく、BigDecimalでは完全に一貫しているわけではありませんが、この場合、John SmithとJohn Doeを同じ人。

+1

OPによれば、彼は 'Object.equals()'を使用したいと考えています。等価)インスタンス。 – Thomas

+0

ピーターに感謝しますが、私はセットが他のジョンを落とすことを望んでいました。それが意図されています。 (私の実際のユースケースはもっと複雑になりますが、それは時間間隔が重ならないことです)。だから、あなたはequalsとの矛盾に何の問題も見ませんか?それは良いですか、またはリース受け入れ可能な練習でですか? – Moritz

+0

@ user2055010技術的には、契約を破ることはできますが、めったに良い考えではありません。どのレコードがジョンのために保持したいのですか?それは、最初か最後かを保証されていないからです。すべてのファーストネームを保持したい場合は、ファーストネームの 'Set 'を使用します。おそらく '' Map > ' –

0

私がJava 8を使用している場合は、Comparatorでユーティリティメソッドを使用することをお勧めします。例えば

SortedSet<Person> people = new TreeSet<>(Comparator.comparing(Person::getName)); 

ビルトインメソッドに実装のすべての複雑さを残します。また、さまざまな利用可能なメソッドを利用して、複数のソート基準を定義し、NULLなどを扱うこともできます。これらのメソッドはAPIの一部であるため、将来のリリースでコードが壊れないと確信することができます。

関連する問題