为什么如果compareTo()返回0就暗示对象相等?

发布于 2024-12-02 01:45:01 字数 2736 浏览 1 评论 0原文

让我们有一个Person 类。人有名字和身高。

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

预期两个不同的人可以具有相同高度的情况似乎是合理的,但例如 TreeSet 的行为类似于compareTo()==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}

你知道为什么会这样吗?

Let's have a class Person. Person has a name and height.

Equals and hashCode() takes into account only name. Person is comparable (or we implement comparator for it, does not matter which one). Persons are compared by height.

It seems reasonable to expect a situation where two different persons can have same height, but e.g. TreeSet behaves like compareTo()==0 means equals, not merely same size.

To avoid this, comparison can secondarily look at something else if size is the same, but then it cannot be used to detect same sized different objects.

Example:

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()));
}
}

result:

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}

Do you have idea why it is so?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

耶耶耶 2024-12-09 01:45:01

java.util.SortedSet javadoc 中摘录:

请注意,排序集维护的顺序(无论是否
提供了显式比较器)必须与 equals 一致,如果
排序集就是正确实现Set接口。 (参见
可比较接口或Comparator接口进行精确定义
与 equals 一致。)之所以如此,是因为 Set 接口是
根据 equals 运算定义,但排序集执行
所有元素比较都使用其compareTo(或compare)方法,所以
此方法认为相等的两个元素是
从排序集的角度来看,相等。有序集的行为是
定义良好,即使其顺序与 equals 不一致;它只是
不遵守 Set 接口的一般契约。

因此,换句话说,SortedSet 打破(或“扩展”)了 Object.equals()Comparable.compareTo 的一般约定。请参阅 compareTo 的合约:

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

Extract from the java.util.SortedSet javadoc:

Note that the ordering maintained by a sorted set (whether or not
an explicit comparator is provided) must be consistent with equals if
the sorted set is to correctly implement the Set interface. (See the
Comparable interface or Comparator interface for a precise definition
of consistent with equals.) This is so because the Set interface is
defined in terms of the equals operation, but a sorted set performs
all element comparisons using its compareTo (or compare) method, so
two elements that are deemed equal by this method are, from the
standpoint of the sorted set, equal. The behavior of a sorted set is
well-defined even if its ordering is inconsistent with equals; it just
fails to obey the general contract of the Set interface.

Hence, in other words, SortedSet breaks (or "extends") the general contracts for Object.equals() and Comparable.compareTo. See the contract for compareTo:

It is strongly recommended, but not strictly required that
(x.compareTo(y)==0) == (x.equals(y)). Generally speaking, any class
that implements the Comparable interface and violates this condition
should clearly indicate this fact. The recommended language is "Note:
this class has a natural ordering that is inconsistent with equals."

誰認得朕 2024-12-09 01:45:01

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

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

(来自 JDK 1.6 Javadocs

It is recommended that compareTo only returns 0, if a call to equals on the same objects would return true:

The natural ordering for a class C is said to be consistent with equals if and only if e1.compareTo(e2) == 0 has the same boolean value as e1.equals(e2) for every e1 and e2 of class C. Note that null is not an instance of any class, and e.compareTo(null) should throw a NullPointerException even though e.equals(null) returns false.

(From the JDK 1.6 Javadocs)

变身佩奇 2024-12-09 01:45:01

TreeSet 不使用哈希码和相等性进行操作 - 它仅根据您提供的比较器进行操作。请注意,Javadoc 指出:

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

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

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

TreeSet doesn't operate using hash codes and equality - it only operates on the basis of the comparator you give it. Note that the Javadoc states:

Note that the ordering maintained by a set (whether or not an explicit comparator is provided) must be consistent with equals if it is to correctly implement the Set interface. (See Comparable or Comparator for a precise definition of consistent with equals.) This is so because the Set interface is defined in terms of the equals operation, but a TreeSet instance performs all element comparisons using its compareTo (or compare) method, so two elements that are deemed equal by this method are, from the standpoint of the set, equal. The behavior of a set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface.

In your case, your comparison *isn't consistent with equals, so your set doesn't obey the general contract of Set.

Why not just add more aspects to the comparison so that only equal elements compare with a result of 0?

猥琐帝 2024-12-09 01:45:01

当高度相等时,您可以通过使用 name 进行另一次比较来修复此问题,

@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

you can fix it by using name for another comparison when the heights are equal

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

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

since names are unique this will only return 0 if this.equals(o)

戏蝶舞 2024-12-09 01:45:01

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

所以你比较一下就好了与您记录的 equals 上使用的标准不同。

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

It is strongly recommended, but not strictly required that (x.compareTo(y)==0) == (x.equals(y)) [1]

So it's fine that you compare with a different criteria than the used on equals granted that you document it.

However it would be better if your compare by the same criteria and if needed provide a custom comparator which works on the new criteria ( height in the case of Person )

超可爱的懒熊 2024-12-09 01:45:01

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

When you give Person a Comparator that compares instances on the height attribute of the Person, it really means that two Person instances are the same if they have the same height.
You will have to make a Comparator that is specific for class Person.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文