Почему Set.contains() не использует o.equals()?

У меня есть TreeSet, содержащий обертки, которые хранят объект Foo в определенном position, определенном так:

class Wrapper implements Comparable<Wrapper> {
  private final Foo foo;
  private final Double position;

  ...

  @Override boolean equals(Object o) {

    ... 

    if(o instanceof Wrapper)
        return o.getFoo().equals(this.foo);

    if(o instanceof Foo)
        return o.equals(this.foo);
  }

  @Override public int compareTo(MarkerWithPosition o) {
      return position.compareTo(o.getPosition());
  }
}

NavigableSet<Wrapper> fooWrappers = new TreeSet<Wrapper>();

потому что я хочу, чтобы мой TreeSet был заказан position, но доступен для поиска foo. Но когда я выполняю эти операции:

Foo foo = new Foo(bar);
Wrapper fooWrapper = new Wrapper(foo, 1.0);
fooWrappers.add(fooWrapper);

fooWrapper.equals(new Wrapper(new Foo(bar), 1.0));
fooWrapper.equals(new Foo(bar));
fooWrappers.contains(fooWrapper);
fooWrappers.contains(new Wrapper(foo, 1.0));
fooWrappers.contains(new Wrapper(new Foo(bar), 1.0));
fooWrappers.contains(new Wrapper(foo, 2.0));
fooWrappers.contains(foo);

Я получил:

true
true
true
true
true
false
Exception in thread "main" java.lang.ClassCastException: org.gridqtl.Marker cannot be cast to java.lang.Comparable
    at java.util.TreeMap.getEntry(TreeMap.java:325)
    at java.util.TreeMap.containsKey(TreeMap.java:209)
    at java.util.TreeSet.contains(TreeSet.java:217)

когда я ожидаю, что все они вернут true, похоже, что TreeSet.contains не использует мой метод equals в качестве API предлагает. Есть ли другой метод, который мне нужно перезаписать?


person bountiful    schedule 30.07.2012    source источник
comment
возможный дубликат проблемы Treeset.contains()   -  person Miquel    schedule 30.07.2012
comment
Именно потому, что сравнение в TreeSet не соответствует равенству   -  person Geek    schedule 30.07.2012


Ответы (2)


TreeSet — это реализация Set, которая действительно использует compareTo, как описано в javadoc - выделение мое:

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

person assylias    schedule 30.07.2012
comment
Означает ли это, что я также должен создать compareTo(Foo)? Но тогда мой Comparable не может быть параметризован. - person bountiful; 30.07.2012
comment
@fophillips, ты действительно не можешь делать то, что пытаешься сделать. Вы не можете заставить TreeMap упорядочиваться одним способом, но искать другим способом. Первое, что я бы попробовал, это остановить ваш метод equals от возни с разными типами, как это есть в настоящее время, и выполнить явный линейный поиск по набору... что вам все равно нужно будет сделать, так как вы хотите, чтобы порядок отличаться от поиска. - person Louis Wasserman; 30.07.2012
comment
Это означает, что foo1.equals(foo2) должно быть эквивалентно foo1.compareTo(foo2) == 0, иначе вы получите неожиданные результаты. Здесь это не так, потому что Wrapper(foo, 1) равен Wrapper(foo, 2), но compareTo не возвращает 0. Возможно, вы захотите пересмотреть свой дизайн. - person assylias; 30.07.2012
comment
Но JavaDoc утверждает, что настоятельно рекомендуется, но не строго требуется, чтобы (x.compareTo(y)==0) == (x.equals(y)), поэтому я не уверен, почему то, что я пытаюсь сделать, так неправильно . - person bountiful; 30.07.2012
comment
Это рекомендуется, потому что нарушение этой рекомендации может привести к странному, запутанному поведению... как вы только что обнаружили. (Поведение по-прежнему четко определено — оно никогда не использует equals, только compareTo — но, как указано в цитируемом Javadoc, ваш компаратор должен быть согласованным с равным, если вы ожидаете, что интерфейс Set гарантирует сохранение .) - person Louis Wasserman; 30.07.2012

TreeSet — это упорядоченный набор.

equals не может предоставить вам информацию о заказе, поэтому TreeSet должен использовать что-то другое.

Это «что-то еще» — интерфейс Comparable или его двоюродный брат Comparator интерфейс.

Оба интерфейса предоставляют информацию о том, как заказать 2 объекта класса.

person Alexander Pogrebnyak    schedule 30.07.2012