2010-12-03 4 views
10

Предположим, мне нужно 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; 
    } 
}); 

Update:

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; 
    } 
}); 

Update 2:

Would System.identityHashCode(x) быть лучше, чем hashCode(), если мне не нужен стабильный сорт?

+0

Итак, вы хотите написать свой собственный компаратор для упорядочивания структуры в своем дереве? – 2010-12-03 09:50:11

+0

@ The Elite Gentleman, да. – 2010-12-03 09:53:06

+0

один из этих возвратов после `strictliBigger` должен быть -1 – 2010-12-03 10:16:46

ответ

9

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

От SortedSet docs:

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

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


Боюсь SortedSet это просто не то, что вы хотите, и не будет гуавы MultiSet быть адекватной (потому что он не позволит вам самостоятельно получить несколько одинаковых элементов). Я думаю, что вам нужен SortedList. Нет такого зверя, которого я знаю (возможно, в коллекциях коллекций, но это немного на стороне наследства), поэтому я применил один из них, используя Guasta ForwardingList в качестве базового класса. Вкратце: этот список делегирует почти все на ArrayList, который он использует внутренне, но использует Collections.binarySearch() в своем методе add(), чтобы найти правильное положение вставки, и он генерирует UnsupportedOperationException по всем необязательным методам интерфейсов и 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(); } 

} 
+0

Я обновляю свой вопрос. Если будет лучше переместить всю сравнительную логику в компаратор, то будет такая согласованность. – 2010-12-03 10:14:28

+1

Я обновил свой ответ. SortedSet, вероятно, не подходит для вас. – 2010-12-03 13:39:27

+0

Ох. Сегодня вы кодируете для меня два класса. Большое спасибо. – 2010-12-03 14:22:00

2

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

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

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

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

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

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

+1

+1 для «вы не можете заказать объекты по hashcode» – 2010-12-03 09:52:41

+0

Вопрос обновлен. – 2010-12-03 10:08:42

1

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

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

+0

Если o1.equals (o2), их можно считать дубликатами. – 2010-12-03 10:10:19

1

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

1
int res = o1.compareTo(o2); 

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

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


Редактировать @Stas, The System.identityHashCode(Object x); еще не поможет.Причина описана в Javadoc:

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

2

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

2

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

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

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

1

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

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

Guavaarbitrary() Ordering реализует Comparator с использованием System.identityHashCode().

1

Да, как указано выше, 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

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

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

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

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

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

1

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