簡體   English   中英

為什么如果 compareTo() 返回 0 則暗示對象相等?

[英]Why it is implied that objects are equal if compareTo() returns 0?

讓我們有一個類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}

你知道為什么會這樣嗎?

摘自java.util.SortedSet javadoc:

請注意,如果排序集要正確實現 Set 接口,則排序集維護的排序(無論是否提供顯式比較器)必須與 equals 一致。 (請參閱 Comparable 接口或 Comparator 接口以了解與 equals 一致的精確定義。)這是因為 Set 接口是根據 equals 操作定義的,但有序集合使用其 compareTo(或 compare)方法執行所有元素比較,所以被這個方法認為相等的兩個元素,從有序集合的角度來看,是相等的。 有序集合的行為是明確定義的,即使它的排序與 equals 不一致; 它只是不遵守 Set 接口的一般約定。

因此,換句話說, SortedSet打破(或“擴展”)了Object.equals()Comparable.compareTo的一般契約。 請參閱compareTo的合同:

強烈推薦,但不嚴格要求 (x.compareTo(y)==0) == (x.equals(y))。 一般來說,任何實現 Comparable 接口並違反此條件的類都應該清楚地表明這一事實。 推薦的語言是“注意:這個類有一個與 equals 不一致的自然順序。”

建議compareTo只返回0 ,如果在相同對象上調用equals將返回true

當且僅當 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

TreeSet不使用哈希碼和相等性進行操作 - 它僅基於您提供的比較器進行操作。 請注意, Javadoc指出:

請注意,如果要正確實現 Set 接口,則集合維護的排序(無論是否提供顯式比較器)必須與 equals 一致。 (請參閱 Comparable 或 Comparator 以獲取與 equals 一致的精確定義。)這是因為 Set 接口是根據 equals 操作定義的,但是 TreeSet 實例使用它的 compareTo(或 compare)方法執行所有元素比較,所以兩個被此方法視為相等的元素,從集合的角度來看,是相等的。 集合的行為是明確定義的,即使它的順序與 equals 不一致; 它只是不遵守 Set 接口的一般約定。

在您的情況下,您的比較 * 與equals不一致,因此您的 set 不遵守Set的一般合同。

為什么不在比較中添加更多方面,以便只有相等的元素與結果 0 進行比較?

當高度相等時,您可以通過使用名稱進行另一次比較來修復它

@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

強烈推薦,但不嚴格要求(x.compareTo(y)==0) == (x.equals(y)) [ 1 ]

因此,您可以與不同的標准進行比較,而不是在您記錄它時使用的equals標准。

但是,如果您按相同的標准進行比較並且需要時提供適用於新標准的自定義比較器(在 Person 的情況下為 height )會更好

當你給 Person 一個 Comparator 來比較 Person 的 height 屬性上的實例時,如果兩個 Person 實例具有相同的高度,那就真的意味着它們是相同的。 您必須創建一個特定於類 Person 的比較器。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM