2015-02-25 3 views
1

JavaDoc сравнивающего утверждает, чтокомпаратор и нарушение договора набора

настоятельно рекомендуется (хотя и не обязательно), что природные упорядоченности согласуется с равными.

Они также приводят пример «странного» поведения, когда (a.equals(b) && c.compare(a,b) != 0).

Теперь, может ли кто-нибудь дать мне пример «странного» поведения в случае (!a.equals(b) && c.compare(a,b) == 0)? Этот второй случай должен происходить чаще, чем первый, потому что легко забыть о реализации equals для сравниваемого типа при реализации Comparator. Трудно придумать пример, не зная о реализации, например, TreeSet.

(Это длинная история, почему этот вопрос имеет отношение ко мне. И это не домашнее задание)

+1

Если вы читаете документацию для 'Set', в ней говорится, что если у вас есть два одинаковых объекта, то в наборе сохраняется только один (или ни один) из них. Это неверно, если это TreeSet с использованием компаратора, который не согласуется с равными. – immibis

ответ

0

JDK реализации коллекций полагаются на поведенческих отношений, как это. Другим хорошим примером является HashSet, который опирается на equals() и hashCode() соглашаясь.

Под «странными» они подразумевают «неопределенные» - нет определения того, как эти классы будут себя вести, если они работают с классами, которые не следуют правилам. Они могут работать отлично, они не могут. Но вы не можете полагаться на них, работая правильно, если ваши классы элементов не придерживаются поведения, описанного в их javadoc.

+0

Конечно, поведение не определено. Но, как правило, существует детерминированное поведение, в зависимости от конкретной реализации (а иногда и от других факторов, таких как версия VM, операционная система, ...) Я надеялся на конкретный пример неустановленного поведения в случае данного контракта нарушение. –

0

Пусть следующий API:

final class Foo { 
    int bar; 
    Foo(int bar) { this.bar = bar; } 
    public int hashCode() { 
     return bar; 
    } 
    public boolean equals(Object o) { 
     return o instanceof Foo && ((Foo)o).bar == bar; 
    } 
} 

static Set<Foo> getSetOpaquely() {???} 

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

Предположим, что набор выполнен как HashSet и определен в терминах equals.

Они также дают пример «странного» поведения, когда (a.equals(b) && c.compare(a,b) != 0)

Пусть я

Set<Foo> mySet = getSetOpaquely(); 
mySet.add(new Foo(1)); 
System.out.println(mySet.add(new Foo(1)); 

Пусть мое удивление, когда печатается true, потому что это был TreeSet с компаратором

(lhs, rhs) -> lhs == rhs ? 0 : 1 

Теперь, может ли кто-нибудь дать мне пример «странного» поведения в случае (!a.equals(b) && c.compare(a,b) == 0)?

Пусть я

Set<Foo> mySet = getSetOpaquely(); 
mySet.add(new Foo(102)); 
System.out.println(mySet.add(new Foo(12)); 

Пусть мое удивление, когда печатается false, потому что это был TreeSet с компаратором

(lhs, rhs) -> Integer.compare(lhs.bar % 10, rhs.bar % 10) 

Теперь, не существует врожденная проблема с определяющим порядком, который не соответствует equals.Дело в том, что TreeSet может вести себя иначе, чем указано в документации для Set.

Это clearly documented:

[...] интерфейс Set определен в терминах операции равна, но TreeSet экземпляр выполняет все сравнения элементов, используя свой метод compareTo (или compare), так что два элементы, которые по этому методу считаются равными, равны, с точки зрения множества. Поведение набора четко определено, даже если его порядок не согласуется с равными; он просто не подчиняется генеральному контракту интерфейса Set.

Пока компаратор не Hacky, и вы знаете, что это TreeSet с конкретным заказом, вы не будете удивлены. (Если это взломано, как (lhs, rhs) -> 1, вы можете быть удивлены.)

1

Вот простая демонстрация. У нас есть класс под названием Strange, который реализует equals и hashCode с использованием сравнения строк без учета регистра, но реализует compareTo с учетом регистра.

class Strange implements Comparable<Strange> { 

    final String s; 

    public Strange(String s) { 
     this.s = s; 
    } 

    @Override 
    public boolean equals(Object o) { 
     // Kind of equals - case insensitive. 
     return (o instanceof Strange) && ((Strange) o).s.equalsIgnoreCase(s); 
    } 

    @Override 
    public int hashCode() { 
     // Consistent with equals. 
     return s.toUpperCase().hashCode(); 
    } 

    @Override 
    public int compareTo(Strange o) { 
     // Exact ordering including case - inconsistent with equals. 
     return s.compareTo(o.s); 
    } 

    @Override 
    public String toString() { 
     return s; 
    } 

} 

public void test() { 
    Set<Strange> set1 = new HashSet<>(); 
    Set<Strange> set2 = new TreeSet<>(); 
    for (String s : new String[]{"Hello", "hello", "Everyone", "everyone"}) { 
     Strange strange = new Strange(s); 
     set1.add(strange); 
     set2.add(strange); 
    } 
    System.out.println("Set1: " + set1); 
    System.out.println("Set2: " + set2); 
} 

Мы получаем - как вы, вероятно, ожидать:

Set1: [Hello, Everyone] 
Set2: [Everyone, Hello, everyone, hello] 

Посмотрите, как ввод строки в TreeSet изменения результат? Это связано с тем, что TreeSet использует compareTo, а HashSet использует equals и hashCode. Это может повредить вещи во многих разных (и наиболее важных) путях, потому что вам не нужно знать, какой тип Set используется за кулисами.

Это показывает (a.equals(b) && a.compareTo(b) != 0) дает странные результаты. Легко показать, что противоположный вопрос (!a.equals(b) && a.compareTo(b) == 0) также демонстрирует странные результаты.

Смежные вопросы