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

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

10 голосов
/ 03 декабря 2010

Предположим, мне нужно 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(), если мне не нужна стабильная сортировка?

Ответы [ 11 ]

9 голосов
/ 03 декабря 2010

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

Из документов SortedSet :

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

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


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

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

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 голосов
/ 03 декабря 2010

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

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

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

2 голосов
/ 03 декабря 2010

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

2 голосов
/ 03 декабря 2010

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

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

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

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

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

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

1 голос
/ 08 декабря 2010

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

1 голос
/ 03 декабря 2010

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

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

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

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

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

1 голос
/ 03 декабря 2010

Да, как уже говорилось выше, 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;
}
1 голос
/ 03 декабря 2010

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

Гуава arbitrary() Заказ реализует Comparator с использованием System.identityHashCode().

1 голос
/ 03 декабря 2010
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(). Хеш Код для нулевой ссылки равен нулю.

1 голос
/ 03 декабря 2010

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

...