22

让我们上课吧Person。人有名字和身高。

Equals 和 hashCode() 仅考虑名称。Person 是可比较的(或者我们为它实现了比较器,不管是哪一个)。人是按身高来比较的。

期望两个不同的人可以具有相同高度的情况似乎是合理的,但是例如。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.SortedSet从javadoc中提取:

请注意,如果有序集合要正确实现 Set 接口,则由有序集合维护的排序(无论是否提供显式比较器)必须与 equals 一致。(请参阅 Comparable 接口或 Comparator 接口以了解与等于一致的精确定义。)这是因为 Set 接口是根据等于操作定义的,但排序集使用其 compareTo(或比较)方法执行所有元素比较,因此从排序集的角度来看,此方法认为相等的两个元素是相等的。一个有序集合的行为是明确定义的,即使它的排序与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

当且仅当 e1.compareTo(e2) == 0 对于类 C 的每个 e1 和 e2 具有与 e1.equals(e2) 相同的布尔值时,才说类 C 的自然排序与 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 一致。(参见 Comparable 或 Comparator 以了解与 equals 一致的精确定义。)这是因为 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);
}

由于名称是唯一的,因此仅在以下情况下才会返回 0this.equals(o)

于 2011-08-29T12:27:03.577 回答
1

强烈建议,但不严格要求 (x.compareTo(y)==0) == (x.equals(y))[ 1 ]

因此,您可以使用与equals您记录它时使用的标准不同的标准进行比较。

但是,如果您按照相同的标准进行比较并且如果需要提供一个适用于新标准的自定义比较器(在 Person 的情况下为 height ),那会更好

于 2011-08-29T12:31:31.573 回答
0

当您给 Person 一个 Comparator 来比较 Person 的 height 属性上的实例时,这实际上意味着如果两个 Person 实例具有相同的高度,则它们是相同的。您将必须创建一个特定于类 Person 的比较器。

于 2011-08-29T12:23:37.033 回答