Согласованные результаты Equals(), но несогласованный результат TreeMap.containsKey()

У меня есть следующий объект Node:

    private class Node implements Comparable<Node>(){
         private String guid();

         ...

         public boolean equals(Node o){
             return (this == o);
         }

         public int hashCode(){
              return guid.hashCode();
         }

         public int compareTo(Node o){
            return (this.hashCode() - o.hashCode());
         }

         ...

    }

И я использую его в следующем TreeMap:

TreeMap<Node, TreeSet<Edge>> nodes = new TreeMap<Node, TreeSet<Edge>>();

Теперь древовидная карта используется в классе Graph для хранения узлов, находящихся в настоящее время в графе, вместе с набором их ребер (из класса Edge). Моя проблема в том, что когда я пытаюсь выполнить:

   public containsNode(n){
        for (Node x : nodes.keySet()) {
            System.out.println("HASH CODE: ");
            System.out.print(x.hashCode() == n.hashCode());
            System.out.println("EQUALS: ");
            System.out.print(x.equals(n));
            System.out.println("CONTAINS: ");
            System.out.print(nodes.containsKey(n));
            System.out.println("N: " + n);
            System.out.println("X: " + x);
            System.out.println("COMPARES: ");
            System.out.println(n.compareTo(x));
            }
        }

Я иногда получаю следующее:

HASHCODE: true РАВНО: true СОДЕРЖИТ: false N: foo X: foo СРАВНЯЕТ: 0

Кто-нибудь знает, что я делаю неправильно? Я все еще новичок во всем этом, поэтому заранее извиняюсь, если упускаю из виду что-то простое (я знаю, что hashCode() не имеет большого значения для TreeMap, но я решил включить это).

edit1: добавлена ​​информация о методе compareTo().


person smessing    schedule 22.04.2010    source источник
comment
equals также может быть проигнорировано TreeMap и TreeSet. Не могли бы вы предоставить compareTo реализацию Node? Это то, что имеет значение с Tree{Map|Set}   -  person Dirk    schedule 23.04.2010


Ответы (3)


Здесь есть несколько неправильных вещей.

  • Вы не переопределили Object.equals. Используйте @Override public boolean equals(Object obj).
  • Существует потенциальная ошибка целочисленного переполнения в compareTo. Это, вероятно, причина этой конкретной ошибки. Это нарушит сортировку, и, следовательно, поиск может не увенчаться успехом.
  • Метод compareTo утверждает, что два экземпляра равны, если совпадает хеш-код (может быть трудно обнаружить ошибку без проверки кода).

О проблеме целочисленного переполнения см. Вопрос Почему мой простой компаратор не работает?

person Tom Hawtin - tackline    schedule 22.04.2010
comment
Спасибо за комментарии. Для compareTo, утверждающего, что два экземпляра равны... это нормально, потому что я знаю, что каждый узел будет иметь отличный guid, это факт проблемы, которую я решаю. Что касается equals(Object obj), я это реализую, но может ли это быть источником моей проблемы, так как я всегда звоню equals(Node)? Я знаю, что это хорошая проблема с практикой программирования, но помимо этого, может ли это действительно вызвать мою проблему? - person smessing; 23.04.2010
comment
Как комментарий к другому ответу, отдельный guid не подразумевает отдельного хэш-кода guid. Я думаю, что проблема заключается в целочисленном переполнении. - person Tom Hawtin - tackline; 23.04.2010
comment
Вероятно, причиной является переполнение int. Я сталкивался с этой проблемой раньше в коде, который я унаследовал, где узел находится в дереве, но из-за проблем с переполнением int функция compareTo потеряла свое транзитивное свойство, что привело к неправильному пути в дереве при выполнении поиска. Я бы изменил функцию compareTo, чтобы вместо этого использовать результат this.guid.compareTo(other.guid). Также, если тот же guid означает один и тот же узел, метод equals также должен использовать this.guid.equals(other.guid). - person Brandon Bodnar; 23.04.2010

TreeSet не использует equals() для определения равенства. Вместо этого он использует Comparator (или Comparable). Чтобы все работало правильно, вы должны следовать правилу согласованность с равными:

«Упорядочивание, налагаемое компаратором c на набор элементов S, называется согласованным с равным тогда и только тогда, когда c.compare(e1, e2)==0 имеет то же логическое значение, что и e1.equals(e2) для каждого e1 и e2 в S".

Я предполагаю, что вы не следуете этому правилу (вы не предоставили реализацию метода compareTo). Если правило не соблюдается, набор деревьев не будет вести себя как набор.

Подробнее см. http://eyalsch.wordpress.com/2009/11/23/comparators/.

--РЕДАКТИРОВАТЬ--

Теперь, когда вы предоставили свою реализацию compareTo, стало ясно, что в ней есть изъян. Он может вернуть 0 для 2 узлов, которые не равны (и имеют одинаковый хэш-код). В результате этого вы не можете добавить 2 элемента с одинаковым хеш-кодом в свой TreeSet!

person Eyal Schneider    schedule 22.04.2010
comment
Я добавил свой метод compareTo() и что происходит, когда вызывается n.compareTo(x) выше. Он постоянно возвращает 0, и все же у меня проблемы. - person smessing; 23.04.2010
comment
Это недостаток, безусловно, но, если честно, это не имеет значения. Для этого проекта я знаю, что у каждого узла будет отдельный guid. Кроме того, исправление этого не решит мою текущую проблему, верно? - person smessing; 23.04.2010
comment
Отдельный guid != отличный хэш-код, если только не гарантируется, что каждый guid имеет отдельный хэш-код (что я считаю маловероятным). Две строки могут быть разными, но сталкиваться с одним и тем же хэш-кодом. - person Brandon Bodnar; 23.04.2010
comment
Четкий хэш-код guid важен. Но это проблема дня рождения на планете с четырьмя миллиардами дней в году. Я думаю, что целочисленное переполнение более вероятно. - person Tom Hawtin - tackline; 23.04.2010
comment
Другой недостаток (который также не связан с вашей проблемой) заключается в том, что ваш метод equals(..) НЕ переопределяет метод equals() объекта. Аргумент должен быть типа Object. - person Eyal Schneider; 23.04.2010

Проверьте свой компаратор.

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

person mindas    schedule 22.04.2010
comment
Я добавил System.out.println(nodes.comparator()), и он возвращает null. Итак, это означает, что он использует естественный порядок, что в данном случае означает использование Node.compareTo() правильно? Я добавил свой метод compareTo() выше. - person smessing; 23.04.2010