22

クラスを作りましょうPerson。人には名前と身長があります。

EqualsおよびhashCode()は、名前のみを考慮します。人は同等です(または、コンパレータを実装します。どちらでも構いません)。人は身長で比較されます。

2人の異なる人が同じ身長を持つことができる状況を予想することは合理的であるように思われますが、例えば。TreeSetは、comapareTo()== 0のように動作します。これは、単に同じサイズではなく、等しいことを意味します。

これを回避するために、サイズが同じである場合、比較は二次的に他の何かを調べることができますが、同じサイズの異なるオブジェクトを検出するために使用することはできません。

例:

import java.util.Comparator;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;

public class Person implements Comparable<Person> {

private final String name;
private int height;

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

public int getHeight() {
    return height;
}

public void setHeight(int height) {
    this.height = height;
}

public String getName() {
    return name;
}

@Override
public int compareTo(Person o) {
    return Integer.compare(height, o.height);
}

public boolean equals(Object obj) {
    if (obj == null) {
        return false;
    }
    if (getClass() != obj.getClass()) {
        return false;
    }
    final Person other = (Person) obj;
    if (!Objects.equals(this.name, other.name)) {
        return false;
    }
    return true;
}

public int hashCode() {
    int hash = 5;
    hash = 13 * hash + Objects.hashCode(this.name);
    return hash;
}

public String toString() {
    return "Person{" + name + ", height = " + height + '}';
}

public static class PComparator1 implements Comparator<Person> {

    @Override
    public int compare(Person o1,
            Person o2) {
        return o1.compareTo(o2);
    }
}

public static class PComparator2 implements Comparator<Person> {

    @Override
    public int compare(Person o1,
            Person o2) {
        int r = Integer.compare(o1.height, o2.height);
        return r == 0 ? o1.name.compareTo(o2.name) : r;
    }
}

public static void test(Set<Person> ps) {
    ps.add(new Person("Ann", 150));
    ps.add(new Person("Jane", 150));
    ps.add(new Person("John", 180));
    System.out.println(ps.getClass().getName());
    for (Person p : ps) {
        System.out.println(" " + p);
    }
}

public static void main(String[] args) {
    test(new HashSet<Person>());
    test(new TreeSet<Person>());
    test(new TreeSet<>(new PComparator1()));
    test(new TreeSet<>(new PComparator2()));
}
}

結果:

java.util.HashSet
 Person{Ann, height = 150}
 Person{John, height = 180}
 Person{Jane, height = 150}

java.util.TreeSet
 Person{Ann, height = 150}
 Person{John, height = 180}

java.util.TreeSet
 Person{Ann, height = 150}
 Person{John, height = 180}

java.util.TreeSet
 Person{Ann, height = 150}
 Person{Jane, height = 150}
 Person{John, height = 180}

なぜそうなのか分かりますか?

4

6 に答える 6

21

java.util.SortedSetjavadocからの抜粋:

ソートされたセットがSetインターフェースを正しく実装するためには、ソートされたセットによって維持される順序(明示的なコンパレーターが提供されているかどうかに関係なく)はequalsと一致している必要があることに注意してください。(equalsとの整合性の正確な定義については、ComparableインターフェイスまたはComparatorインターフェイスを参照してください。)これは、Setインターフェイスがequals操作で定義されているが、並べ替えられたセットがcompareTo(またはcompare)メソッドを使用してすべての要素の比較を実行するためです。したがって、この方法で等しいと見なされる2つの要素は、ソートされたセットの観点からは等しいです。ソートされたセットの動作は、その順序がequalsと矛盾している場合でも明確に定義されています。Setインターフェースの一般的な契約に従わないだけです。

したがって、言い換えると、とSortedSetの一般的な契約を破る(または「拡張する」)。の契約を参照してください:Object.equals()Comparable.compareTocompareTo

(x.compareTo(y)== 0)==(x.equals(y))であることが強く推奨されますが、厳密には必須ではありません。一般的に、Comparableインターフェースを実装し、この条件に違反するクラスは、この事実を明確に示す必要があります。推奨される言語は「注:このクラスには、equalsと矛盾する自然な順序があります」です。

于 2011-08-29T12:24:07.440 に答える
10

同じオブジェクトでへの呼び出しが:をcompareTo返す場合にのみ、を返すことをお勧めします。0equalstrue

クラスCの自然な順序は、e1.compareTo(e2)== 0がクラスCのすべてのe1およびe2に対してe1.equals(e2)と同じブール値を持つ場合にのみ、equalsと一致すると言われます。 nullはどのクラスのインスタンスでもないため、e.equals(null)がfalseを返しても、e.compareTo(null)はNullPointerExceptionをスローする必要があります。

JDK 1.6 Javadocsから)

于 2011-08-29T12:22:48.783 に答える
7

TreeSetハッシュコードと等式を使用して動作するのではなく、指定したコンパレータに基づいてのみ動作します。Javadocには次のように記載されていることに注意してください。

Setインターフェースを正しく実装するためには、セットによって維持される順序(明示的なコンパレーターが提供されているかどうかに関係なく)がequalsと一致している必要があることに注意してください。(equalsとの整合性の正確な定義については、ComparableまたはComparatorを参照してください。)これは、Setインターフェイスがequals操作で定義されているためですが、TreeSetインスタンスはcompareTo(またはcompare)メソッドを使用してすべての要素の比較を実行します。この方法で等しいと見なされる要素は、セットの観点からは等しいです。セットの動作は、その順序がequalsと矛盾している場合でも明確に定義されています。Setインターフェースの一般的な契約に従わないだけです。

あなたの場合、あなたの比較は*と一致してequalsいないので、あなたのセットはの一般的な契約に従わないSet

等しい要素のみが結果0と比較されるように、比較にさらにアスペクトを追加しないのはなぜですか?

于 2011-08-29T12:24:02.863 に答える
2

高さが等しい場合は、別の比較のために名前を使用して修正できます

@Override
public int compareTo(Person o) {
    if(height == o.height)return name.compareTo(o.name);

    return Integer.compare(height, o.height);
}

名前は一意であるため、これは次の場合にのみ0を返しますthis.equals(o)

于 2011-08-29T12:27:03.577 に答える
1

強くお勧めしますが、厳密には必須で (x.compareTo(y)==0) == (x.equals(y))はありません[ 1 ]

equalsしたがって、それを文書化することを前提として使用されているものとは異なる基準と比較することは問題ありません。

ただし、同じ基準で比較し、必要に応じて、新しい基準(Personの場合は高さ)で機能するカスタムコンパレータを提供するとよいでしょう。

于 2011-08-29T12:31:31.573 に答える
0

Personのheight属性でインスタンスを比較するComparatorをPersonに与えると、実際には、2つのPersonインスタンスが同じ高さである場合は同じであることを意味します。クラスPersonに固有のコンパレータを作成する必要があります。

于 2011-08-29T12:23:37.033 に答える