Как реализовать Comparable, чтобы он соответствовал идентичности равенства

У меня есть класс, для которого равенство (согласно equals()) должно быть определено идентификатором объекта, то есть this == other.

Я хочу реализовать Comparable для упорядочения таких объектов (скажем, по некоторому getName()). Чтобы соответствовать equals(), compareTo() не должно возвращать 0, даже если два объекта имеют одинаковое имя.

Есть ли способ сравнения объектов идентичности в смысле compareTo? Я мог бы сравнить System.identityHashCode(o), но он все равно вернул бы 0 в случае коллизий хешей.

Ответ 1

Я думаю, что реальный ответ здесь таков: не используйте Comparable. Реализация этого интерфейса подразумевает, что ваши объекты имеют естественный порядок. Вещи, которые "равны", должны быть в одном и том же месте, когда вы следите за этой мыслью.

Если вообще, вы должны использовать собственный компаратор... но даже это не имеет особого смысла. Если вещь, которая определяет a <b..., не может дать вам a == b (когда a и b "равны" в соответствии с вашим <отношением), тогда весь подход сравнения нарушается для вашего варианта использования.

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

Ответ 2

Вы можете добавить второе свойство (скажем, int id или long id), которое будет уникальным для каждого экземпляра вашего класса (вы можете иметь static переменную counter и использовать ее для инициализации id в вашем конструкторе).

Затем ваш метод compareTo может сначала сравнить имена, а если имена равны, сравнить id s.

Поскольку у каждого экземпляра свой id, compareTo никогда не вернет 0.

Ответ 3

По определению, присваивая каждому объекту универсальный уникальный идентификатор (UUID) (или глобальный уникальный идентификатор (GUID)) в качестве его свойства идентичности, UUID сопоставим и согласуется с равными. В Java уже есть класс UUID, и после его генерации вы можете просто использовать строковое представление для сохранения. Выделенное свойство также гарантирует, что идентичность стабильна во всех версиях/потоках/машинах. Вы также можете просто использовать инкрементный идентификатор, если у вас есть метод обеспечения того, что все получает уникальный идентификатор, но использование стандартной реализации UUID защитит вас от проблем, связанных с объединением множеств и параллельными системами, генерирующими данные одновременно.

Если вы используете что-либо еще для сопоставимого, это означает, что оно сопоставимо способом, отличным от его идентичности/ценности. Таким образом, вам нужно будет определить, что сопоставимо означает для этого объекта, и задокументировать это. Например, люди сравнимы по имени, DOB, росту или комбинации по порядку старшинства; наиболее естественно по имени как соглашение (для более легкого поиска людьми), которое отделено от, если два человека - то же самое лицо. Вы также должны будете признать, что сравнение и равные не связаны, потому что они основаны на разных вещах.

Ответ 4

Предполагая, что с двумя объектами с одинаковыми именами, если equals() возвращает false compareTo() не должно возвращать 0. Если это то, что вы хотите сделать, то может помочь следующее:

  • Переопределите hashcode() и убедитесь, что он не зависит исключительно от name
  • compareTo() следующим образом:
public void compareTo(MyObject object) {
    this.equals(object) ? this.hashcode() - object.hashcode() : this.getName().compareTo(object.getName());
}

Ответ 5

У вас есть уникальные объекты, но, как сказал Эран, вам может понадобиться дополнительный код счетчика/перефразировки для любых коллизий.

private static Set<Pair<C, C> collisions = ...;

@Override
public boolean equals(C other) {
    return this == other;
}

@Override
public int compareTo(C other) {
    ...
    if (this == other) {
        return 0
    }
    if (super.equals(other)) {
        // Some stable order would be fine:
        // return either -1 or 1
        if (collisions.contains(new Pair(other, this)) {
            return 1;
        } else if (!collisions.contains(new Pair(this, other)) {
            collisions.add(new Par(this, other));
        }
        return 1;
    }
    ...
}

Так что иди с ответом Эрана или поставь требование как таковое под сомнение.

  • Можно считать, что издержки неидентичных 0 сравнений пренебрежимо малы.
  • Можно взглянуть на идеальные хеш-функции, если в какой-то момент времени больше не создаются экземпляры. Это означает, что у вас есть коллекция всех экземпляров.

Ответ 6

В то время как я придерживаюсь своего первоначального ответа, что вы должны использовать свойство UUID для стабильной и последовательной настройки сравнения/равенства, я подумал, что я продолжу отвечать на вопрос "как далеко вы могли бы пойти, если бы вы ДЕЙСТВИТЕЛЬНО были параноиком и хотели гарантированная уникальная идентичность для сопоставимых ".

Короче говоря, если вы не доверяете уникальности UUID или уникальности идентичности, просто используйте столько UUID, сколько нужно, чтобы доказать, что Бог активно сговорился против вас. (Обратите внимание, что, хотя технически не гарантируется, что не выдается исключение, необходимость в 2 UUID должна быть избыточной в любой разумной вселенной.)

import java.time.Instant;
import java.util.ArrayList;
import java.util.UUID;

public class Test implements Comparable<Test>{

    private final UUID antiCollisionProp = UUID.randomUUID();
    private final ArrayList<UUID> antiuniverseProp = new ArrayList<UUID>();

    private UUID getParanoiaLevelId(int i) {
        while(antiuniverseProp.size() < i) {
            antiuniverseProp.add(UUID.randomUUID());
        }

        return antiuniverseProp.get(i);
    }

    @Override
    public int compareTo(Test o) {
        if(this == o)
            return 0;

        int temp = System.identityHashCode(this) - System.identityHashCode(o);
        if(temp != 0)
            return temp;

        //If the universe hates you
        temp = this.antiCollisionProp.compareTo(o.antiCollisionProp);
        if(temp != 0)
            return temp;

        //If the universe is activly out to get you
        temp = System.identityHashCode(this.antiCollisionProp) - System.identityHashCode(o.antiCollisionProp);;
        if(temp != 0)
            return temp;

        for(int i = 0; i < Integer.MAX_VALUE; i++) {
            UUID id1 = this.getParanoiaLevelId(i);
            UUID id2 = o.getParanoiaLevelId(i);
            temp = id1.compareTo(id2);
            if(temp != 0)
                return temp;

            temp = System.identityHashCode(id1) - System.identityHashCode(id2);;
            if(temp != 0)
                return temp;
        }

        // If you reach this point, I have no idea what you did to deserve this
        throw new IllegalStateException("RAGNAROK HAS COME! THE MIDGARD SERPENT AWAKENS!");
    }

}