Компаратор и равно()

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

Итак, вопрос: какие недостатки у меня будут из этого кода:

class Foo implements Comparable<Foo>{}
new TreeSet<Foo>(new Comparator<Foo>(){
    @Override
    public int compare(Foo o1, Foo o2) {
        int res = o1.compareTo(o2);
        if(res == 0 || !o1.equals(o2)){
            return o1.hashCode() - o2.hashCode();
        }
        return res;
    }
});

Обновление

Ok. Если это всегда будет согласованность между методами equals(), hashcode() и compareTo(), как @S.P.Floyd - seanizer и другие. Если было бы лучше или даже хорошо, если я удалю интерфейс Comparable и переведу эту логику в Comparator (я могу сделать это без сломанной инкапсуляции)? Так будет:

class Foo{}
new TreeSet<Foo>(new Comparator<Foo>(){
    @Override
    public int compare(Foo o1, Foo o2) {
        //some logic start
        if(strictliBigger(o1, o2)){ return 1;}
        if(strictliBigger(o2, o1)){ return -1;}
        //some logic end
        if(res == 0 || !o1.equals(o2)){
            return o1.hashCode() - o2.hashCode();
        }
        return res;
    }
});

Обновление 2:

Будет ли System.identityHashCode(x) лучше, чем hashcode(), если мне не нужна стабильная сортировка?

Ответ 1

Хотя это может сработать, это далеко не лучший опыт.

Из Документы SortedSet:

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

Для объектов, реализующих Comparable, всегда должна быть согласованность между методами equals(), hashcode() и compareTo().


Я боюсь, что SortedSet просто не то, что вы хотите, и Guava MultiSet не будет адекватным (потому что он не позволит вам самостоятельно получать несколько одинаковых элементов). Я думаю, что вам нужно SortedList. Нет такого зверя, которого я знаю (возможно, в коллекциях коллекций, но это немного на стороне наследства), поэтому я применил его для вас, используя Guava ForwardingList в качестве базового класса. Короче говоря, этот список делегирует почти все в ArrayList, который он использует внутренне, но использует Collections.binarySearch() в нем метод add(), чтобы найти правильную позицию вставки и выдает UnsupportedOperationException для всех необязательных методов List и ListIterator, которые добавляют или задают значения в заданной позиции.

Конструкторы идентичны конструкторам ArrayList, но для каждой из них есть также вторая версия с пользовательским Comparator, Если вы не используете пользовательский Компаратор, элементы списка должны реализовать Comparable или RuntimeException будут возникать во время сортировки.

public class SortedArrayList<E> extends ForwardingList<E> implements
    RandomAccess{

    private final class ListIteratorImpl extends ForwardingListIterator<E>{
        private final int start;
        public ListIteratorImpl(final int start){
            this.start = start;
        }

        @Override
        public void set(E element){throw new UnsupportedOperationException();}

        @Override
        public void add(E element){throw new UnsupportedOperationException();}

        @Override
        protected ListIterator<E> delegate(){return inner.listIterator(start);};

    }

    private Comparator<? super E> comparator;

    private List<E> inner;

    public SortedArrayList(){this(null, null, null);}

    @SuppressWarnings("unchecked")
    private SortedArrayList(
        final List<E> existing,
        final Collection<? extends E> values,
        final Comparator<? super E> comparator
    ){
        this.comparator =
            (Comparator<? super E>)
               (comparator == null
                   ? Ordering.natural()
                   : comparator   );
        inner = (
            existing == null
                ? (values == null
                      ? new ArrayList<E>(values)
                      : new ArrayList<E>()
                   )
                : existing;
    }

    public SortedArrayList(final Collection<? extends E> c){
        this(null, c, null);
    }

    public SortedArrayList(final Collection<? extends E> c,
        final Comparator<? super E> comparator){
        this(null, c, comparator);
    }

    public SortedArrayList(final Comparator<? super E> comparator){
        this(null, null, comparator);
    }

    public SortedArrayList(final int initialCapacity){
        this(new ArrayList<E>(initialCapacity), null, null);
    }

    public SortedArrayList(final int initialCapacity,
        final Comparator<? super E> comparator){
        this(new ArrayList<E>(initialCapacity), null, comparator);
    }

    @Override
    public boolean add(final E e){
        inner.add(
            Math.abs(
                Collections.binarySearch(inner, e, comparator)
            ) + 1,
            e
        );
        return true;
    }

    @Override
    public void add(int i, E e){throw new UnsupportedOperationException();}

    @Override
    public boolean addAll(final Collection<? extends E> collection){
        return standardAddAll(collection);
    }

    @Override
    public boolean addAll(int i,
        Collection<? extends E> es){
        throw new UnsupportedOperationException();
    }

    @Override
    protected List<E> delegate(){ return inner; }

    @Override
    public List<E> subList(final int fromIndex, final int toIndex){
        return new SortedArrayList<E>(
            inner.subList(fromIndex, toIndex),
            null,
            comparator
        );
    }

    @Override
    public ListIterator<E> listIterator(){ return new ListIteratorImpl(0); }

    @Override
    public ListIterator<E> listIterator(final int index){
        return new ListIteratorImpl(index);
    }

    @Override
    public E set(int i, E e){ throw new UnsupportedOperationException(); }

}

Ответ 2

hashcode() не гарантирует less than или greater than. compare() и equals() должны давать то же значение, но не обязательно.

Насколько я понимаю из вашего запутанного кода (без обид):), вы хотите добавить дубликаты в TreeSet. По этой причине вы придумали эту реализацию. Вот почему вы не можете поместить их в TreeSet, цитируя из документов,

Поведение набора хорошо определено даже если его порядок несовместим с равными; он просто не подчиняется общий контракт интерфейса Set.

Итак, вам нужно что-то сделать с помощью метода yor equals(), поэтому он никогда не сможет вернуть истину. Лучшая реализация -

public boolean equals(Object o) {
    return false;
}

Кстати, если я прав в своем понимании, почему бы вам не использовать List и не сортировать это.

Ответ 3

Остерегайтесь: даже для двух Foos f1, f2 с f1 != f2 вы можете получить f1.hashCode() == f2.hashCode()! Это означает, что вы не получите стабильную сортировку с помощью метода compare.

Ответ 4

В Java нет правила, в котором говорится, что хэш-коды двух объектов должны быть разными только потому, что они не равны (поэтому o1.hashCode() - o2.hashCode() может возвращать 0 в вашем случае).

Также поведение equals() должно соответствовать результатам из compareTo(). Это не обязательно, но если вы не можете сохранить это, это говорит о том, что ваш дизайн имеет большой недостаток.

Я настоятельно рекомендую посмотреть другие поля объектов и использовать некоторые из них для расширения вашего сравнения, чтобы вы получили значение != 0 для объектов equals() == false.

Ответ 5

Очень интересный вопрос. Насколько я понимаю, ваша проблема - это повторяющиеся элементы.

Я думаю, что если o1.equals(o2), их хэш-коды также могут быть равны. Это зависит от реализации hashCode() в вашем классе Foo. Поэтому я предлагаю вам вместо этого использовать System.identityHashCode(x).

Ответ 6

У вас есть класс Foo, который сопоставим, но хочет использовать другую сортировку в структуре TreeSet<Foo>. Тогда ваша идея - правильный способ сделать это. Используйте этот конструктор для "перенацеления" естественной сортировки Foo.

Ответ 7

int res = o1.compareTo(o2);

if(res == 0 || !o1.equals(o2)){
    return o1.hashCode() - o2.hashCode();
}

Может быть проблематичным, так как если 2 объекта равны (т.е. в вашем res == 0), то эти 2 объекта возвращают один и тот же хэш-код. Хаскоды не уникальны для каждого объекта.


Изменить @Stas, System.identityHashCode(Object x); по-прежнему вам не поможет. Причина описана в javadoc:

Возвращает тот же хэш-код для данный объект, который будет возвращен метод по умолчанию hashCode(), независимо от того, является ли данный объект класс переопределяет hashCode(). Хэш код нулевой ссылки равен нулю.

Ответ 8

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

Как отмечали другие, hashCode() не является хорошим кандидатом, потому что значения hashCode() обоих элементов могут быть легко равны. System.identityHashCode() может быть лучшим выбором, но все еще не идеальна, так как даже identityHashCode() не гарантирует уникальные значения либо

Guava arbitrary() Заказ реализует a Comparator с помощью System.identityHashCode().

Ответ 9

Да, как было сказано выше, hashCode() не является безопасным для использования здесь. Но если вы не заботитесь о упорядочении объектов, которые равны в терминах o1.compareTo(o2) == 0, вы можете сделать что-то вроде:

public int compare(Foo o1, Foo o2) {
        int res = o1.compareTo(o2);
        if (res == 0 && !o1.equals(o2)) {
            return -1;
        }
        return res;
}

Ответ 10

Здесь есть несколько проблем:

  • Хэш-коды обычно не уникальны, и, в частности, System.identityHashCode не будет уникальным для неопределенно современных JVM.

  • Это не вопрос стабильности. Мы сортируем массив, но создаем древовидную структуру. Коллизии хеш-кода вызовут compare для возврата нуля, что для TreeSet означает, что один объект побеждает, а другой отбрасывается - он не деградирует в связанный список (ключ имеет значение "Установить" в имени).

  • Обычно существует проблема с переполнением целых чисел с вычитанием одного хеш-кода из другого. Это означает, что сравнение не будет транзитивным (т.е. Оно будет нарушено). Как повезло, при реализации Sun/Oracle System.identityHashCode всегда возвращает положительные значения. Это означает, что обширное тестирование, вероятно, не обнаружит этого конкретного типа ошибок.

Я не верю, что есть хороший способ достичь этого, используя TreeSet.

Ответ 11

Две точки могут быть релевантными, и они показывают, что возврат в одной ситуации показан как -1, и это зависит от того, разрешено ли отрицательное значение в переменной параметра функции или в соответствующей стране использования, а также если метод использование разрешено. Существуют стандартные методы организации данных, такие как сортировка или сортировка сортировки, а описание или код документа обычно доступны в национальном органе, если копия не находится на вашем рабочем месте. Использование сравнений, таких как больше или меньше, может ускорить выполнение кода и избегать использования прямого сравнения для равенства путем подразумеваемого перехода к более позднему script или коду.