Java,如果compareTo()返回0,为什么暗示对象是相等的?

时间:2011-08-29 12:19:26

标签: java equals comparator comparable

我们有一个班级Person。人有名字和身高。

Equals和hashCode()仅考虑名称。人是可比的(或者我们为它实施比较,无论哪一个)。人数按身高进行比较。

预计两个不同的人可以拥有相同身高的情况似乎是合理的,例如。 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}

你知道为什么会这样吗?

6 个答案:

答案 0 :(得分:16)

摘自java.util.SortedSet javadoc:

  

请注意由排序集维护的排序(无论是否   提供明确的比较器)必须与equals if一致   排序集是为了正确实现Set接口。 (见   可比较的接口或Comparator接口,用于精确定义   与equals一致的。)这是因为Set接口是   根据equals操作定义,但是有序集合执行   所有元素比较使用compareTo(或compare)方法,所以   通过这种方法被认为是平等的两个要素是   排序集的立场,相等。排序集的行为是   定义明确,即使其排序与equals不一致;它只是   无法遵守Set接口的常规合同。

因此,换句话说,SortedSet打破(或“扩展”)Object.equals()Comparable.compareTo的一般合约。查看compareTo的合同:

  

强烈建议,但并非严格要求   (x.compareTo(y)== 0)==(x.equals(y))。一般来说,任何课程   实现Comparable接口并违反此条件   应该清楚地表明这一事实。推荐的语言是“注意:   这个类的自然顺序与equals不一致。“

答案 1 :(得分:10)

如果对相同对象的compareTo的调用将返回0,则建议equals仅返回true

  

当且仅当e1.compareTo(e2)== 0与c1的每个e1和e2的e1.equals(e2)具有相同的布尔值时,C类的自然排序被认为与equals一致。请注意,null不是任何类的实例,并且e.compareTo(null)应该抛出NullPointerException,即使e.equals(null)返回false。

(来自JDK 1.6 Javadocs

答案 2 :(得分:6)

TreeSet不使用哈希码和相等操作 - 它只在你给它的比较器的基础上运行。请注意Javadoc个州:

  

请注意,如果要正确实现Set接口,则由set维护的排序(无论是否提供显式比较器)必须与equals一致。 (有关与equals一致的精确定义,请参阅Comparable或Comparator。)这是因为Set接口是根据equals操作定义的,但TreeSet实例使用compareTo(或compare)方法执行所有元素比较,因此从集合的角度来看,通过这种方法被认为相等的元素是相等的。集合的行为即使其排序与equals不一致也是明确定义的;它只是没有遵守Set接口的一般合同。

在您的情况下,您的比较*与equals不一致,因此您的集合不遵守Set的一般合同。

为什么不在比较中添加更多方面,以便只有相等的元素与0的结果进行比较?

答案 3 :(得分:1)

你可以通过在高度相等时使用名称进行另一次比较来修复它

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

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

因为名称是唯一的,所以只有this.equals(o)

才会返回0

答案 4 :(得分:1)

强烈建议您使用 {严格 (x.compareTo(y)==0) == (x.equals(y)) [1]

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

然而,如果按照相同的标准进行比较并且如果需要的话,提供一个适用于新标准的自定义比较器(在人的情况下为高度)会更好

答案 5 :(得分:0)

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