Как реализовать Comparable, чтобы он соответствовал идентичности равенства - PullRequest
18 голосов
/ 01 апреля 2019

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

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

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

Ответы [ 6 ]

32 голосов
/ 01 апреля 2019

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

Если вообще, вы должны использовать собственный компаратор ... но даже это не имеет особого смысла. Если вещь, которая определяет a сравнения сломан для вашего варианта использования.

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

6 голосов
/ 01 апреля 2019

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

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

5 голосов
/ 01 апреля 2019

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

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

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

1 голос
/ 02 апреля 2019

Хотя я придерживаюсь своего первоначального ответа, что вы должны использовать свойство 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!");
    }

}
0 голосов
/ 01 апреля 2019

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

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 пренебрежимо малы.
  • Можно заглянуть в идеальные хеш-функции , если в какой-то момент больше не будут созданы экземпляры. Это означает, что у вас есть коллекция всех экземпляров.
0 голосов
/ 01 апреля 2019

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

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