Подтвердить что ты не робот

Java, Почему подразумевается, что объекты равны, если compareTo() возвращает 0?

Пусть имеет класс Person. У человека есть имя и высота.

Равные и hashCode() учитывают только имя. Человек сопоставим (или мы используем компаратор для него, не имеет значения, какой). Люди сравниваются по высоте.

Кажется разумным ожидать ситуации, когда два разных человека могут иметь одинаковую высоту, но, например. TreeSet ведет себя как comapareTo() == 0 означает equals, а не только тот же размер.

Чтобы избежать этого, сравнение может во второй раз посмотреть на что-то другое, если размер одного и того же, но тогда он не может использоваться для обнаружения одинаковых разных объектов.

Пример:

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}

У вас есть идея, почему это так?

4b9b3361

Ответ 1

Извлечь из java.util.SortedSet javadoc:

Обратите внимание, что упорядочение, поддерживаемое сортированным набором (независимо от того, явный компаратор) должен быть согласован с равными, если сортированный набор - это правильно реализовать интерфейс Set. (См. Сопоставимый интерфейс или интерфейс компаратора для точного определения в соответствии с равенствами.) Это происходит потому, что интерфейс Set определяется в терминах операции равенства, но сортированный набор выполняет все сравнения элементов с использованием метода compareTo (или сравнения), поэтому два элемента, которые по этому методу считаются равными точка зрения сортированного множества, равная. Поведение сортированного множества четко определен, даже если его упорядочение не соответствует равным; это просто не выполняет общий контракт интерфейса Set.

Следовательно, другими словами, SortedSet прерывает (или "расширяет" ) общие контракты для Object.equals() и Comparable.compareTo. См. Договор на compareTo:

Настоятельно рекомендуется, но строго не требуется, чтобы (x.compareTo(y) == 0) == (x.equals(y)). Вообще говоря, любой класс который реализует интерфейс Comparable и нарушает это условие должен четко указывать этот факт. Рекомендуемый язык: "Примечание: этот класс имеет естественное упорядочение, которое несовместимо с равными".

Ответ 2

Рекомендуется, чтобы compareTo возвращал 0, если вызов equals на тех же объектах возвращал бы true:

Естественное упорядочение для класса C называется согласованным с равенствами тогда и только тогда, когда e1.compareTo(e2) == 0 имеет то же булево значение, что и e1.equals(e2) для всех e1 и e2 класса C Обратите внимание: null не является экземпляром какого-либо класса, а e.compareTo(null) должен вызывать исключение NullPointerException, даже если e.equals(null) возвращает false.

(Из JDK 1.6 Javadocs)

Ответ 3

TreeSet не работает с использованием хеш-кодов и равенства - он работает только на основе компаратора, который вы ему даете. Обратите внимание, что Javadoc утверждает:

Обратите внимание, что порядок, поддерживаемый набором (будь то явный компаратор), должен быть согласован с равными, если он правильно реализует интерфейс Set. (См. Comparable или Comparator для точного определения соответствия с равными.) Это происходит потому, что интерфейс Set определен в терминах операции equals, но экземпляр TreeSet выполняет все сравнения элементов с помощью метода compareTo (или сравнения), поэтому два элементы, которые по этому методу считаются равными, равны, с точки зрения множества. Поведение множества хорошо определено, даже если его упорядочение не соответствует равным; он просто не подчиняется генеральному контракту интерфейса Set.

В вашем случае ваше сравнение * не соответствует equals, поэтому ваш набор не подчиняется общему контракту Set.

Почему бы просто не добавить больше аспектов для сравнения, так что только равные элементы сравниваются с результатом 0?

Ответ 4

вы можете исправить это, используя имя для другого сравнения, когда высота равна

@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)

Ответ 5

Настоятельно рекомендуется, но не строго требуется (x.compareTo(y)==0) == (x.equals(y)) [1]

Так что отлично, что вы сравниваете с другими критериями, чем используемые в equals, предоставленные вам, чтобы вы его документировали.

Однако было бы лучше, если бы вы сравнили по тем же критериям и, если необходимо, создали пользовательский компаратор, который работает над новыми критериями (высота в случае Person)

Ответ 6

Когда вы даете Person Comparator, который сравнивает экземпляры атрибута height Person, это действительно означает, что два экземпляра Person одинаковы, если они имеют одинаковую высоту. Вам нужно будет сделать Компаратор, специфичный для класса Person.