2011-01-20 2 views
19

Есть ли какая-либо ситуация, когда имеет смысл для класса реализовать свои методы equals() и hashCode() с использованием другого набора полей класса?Java equal() и hashCode() на основе разных полей?

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

+1

С учетом того, что генератор кода Netbeans ошибочен, чтобы дать выбор, если нет веской причины выбирать разные поля. – Raedwald

ответ

22

Ну, equals()должен использовать все поля, используемые hashCode(), так как в противном случае вы можете получить различные хэш-коды для одинаковых объектов. Обратное неверно, но вы можете указать при выборе хеш-кода не принимать во внимание одно конкретное поле. Таким образом, вы можете получить тот же хэш-код для двух неравных объектов, которые отличаются только этим «неиспользуемым» полем (в отличие от естественных столкновений). Вы только хотели бы, чтобы в ситуации, когда вы знали, что столкновения были бы маловероятными, но где вы собираетесь хешировать лот. Я предполагаю, что это очень редко :)

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

+1

«вы могли бы выбрать не учитывать [некоторые поля]», например, если эти поля просто содержат кешированное значение, вычисленное из других полей. – Raedwald

+0

@ Raedwald: Но почему бы вы включить эти поля для равенства в этом случае? Возможно, я неправильно понял ваше предложение ... но вы правы, что предварительно вычисленные поля могут быть релевантными. –

+0

Вы должны ** исключить ** поля, в которых хранятся кешированные значения. – Raedwald

2

Как правило, вы должны использовать те же самые поля. Из equals() документации:

Обратите внимание, что, как правило, необходимо переопределить метод Hashcode всякий раз, когда преодолено этот метод, таким образом, чтобы поддерживать общий контракт метода Hashcode, в котором говорится, что равные объекты должны иметь равные хэш-коды ,

Из документации hashCode():

Если два объекта равен по методе Equals (Object), то вызов метода HashCode на каждом из двух объектов должны производить один и тот же целочисленный результат.

Обратите внимание, что обратное неверно - вы можете иметь два объекта с одной и той же хэш-код, который не равны (Это, как некоторые структуры данных разрешения коллизий)

Итак, теоретически можно использовать подмножество полей полей equals(..) для метода hashCode(), но я не могу думать, если это практическая причина.

+1

Это неправильно. Два объекта с равным хеш-кодом не обязательно должны быть равны, поэтому достаточно использовать подмножество полей, используемых для равных вычислений хеш-кода. См. Ответ Джона Скита. – sfussenegger

+1

@sfussenegger благодарит, исправил мое заявление. – Bozho

+2

Единственные практические причины, о которых я могу думать, - это лень - c'mon, это печатать на лотте;) - и производительность, например. если коллекция способствует равенству. Кроме того, может быть одно или два поля, близкие к уникальным. Кстати, нисходящий канал не был моим, хотя;) – sfussenegger

1

не думаю есть. I blogged about this topic previously - Я думаю, что это недостаток интерфейса в NetBeans, который позволяет вам выбирать их независимо друг от друга. Из моего сообщения в блоге:

Это post from bytes.com делает хорошую работу, объясняя это:

Переопределение метода Hashcode.

В контракте для метода equals действительно должна быть указана другая строка, в которой вы должны перейти к переопределению метода hashCode после переопределения метода equals. Метод hashCode поддерживается для коллекций, основанных на хеше.

Контракт

Опять из спецификации:

Всякий раз, когда он вызывается на одном объекте более чем один раз в ходе исполнения приложения, метод хэш-код должен последовательно возвращать то же самое число, не предусмотрено никакой информации используется при равных сравнениях на объекте. Это целое число не должно оставаться согласованным с одним исполнением приложения на другое выполнение одного и того же приложения. Если два объекта равны в соответствии с методом equals (Object), то вызов метода hashCode для каждого из двух объектов должен приводить к одному и тому же целочисленному результату. Не требуется, чтобы, если два объекта не равны в соответствии с методом equals, вызов метода hashCode для каждого из двух объектов должен производить различные целые результаты. Тем не менее, программист должен знать, что получение отдельных целых результатов для неравных объектов может улучшить производительность хеш-таблиц. Таким образом, равные объекты должны иметь равные хэш-коды. Простым способом гарантировать, что это условие всегда выполняется, является использование тех же атрибутов, которые используются при определении равенства при определении hashCode. Теперь вы должны понять, почему важно переопределять hashCode каждый раз, когда вы переопределяете equals.

Это фраза из последнего абзаца подводит итог: «простой способ убедиться, что это условие выполняется всегда должен использовать одни и те же атрибуты, используемые при определении равенства в определении» хэш-код.

2

Jon Skeet проделал хорошую работу, отвечая на этот вопрос (как всегда). Тем не менее, я хотел бы добавить, что это достоверная реализация для любой реализации равно

public int hashCode() { 
    return 42; 
} 

Естественно, производительность структур данных хэшированных резко деградировать. Тем не менее, лучше убить производительность, чем сломать их. Поэтому, если вы когда-либо решаете переопределить равные, но не видите необходимости предоставлять разумную реализацию hashCode, это путь ленивого человека.

-1

Прочитайте эффективную Java в главе 3: «Always override hashCode when you override equals».

И я думаю, что если ваш объект никогда не будет помещен в коллекцию на основе хэшей, вам не нужно переопределять hashCode.

1

В ответ на ответ Джона Скита я недавно столкнулся с ситуацией, когда мне нужно было реализовать метод hashCode только с подмножеством полей, используемых в методе equals. Сценарий (упрощенного) заключается в следующем:

У меня есть два класса A и B, каждый из которых содержит ссылку на другую в дополнение к определению строкового ключа. Использование автоматического хэш-код и составляет генератор в Eclipse (который, в отличие от Netbeans, только дает возможность использовать одни и те же поля в обоих методах) я в конечном итоге со следующими классами:

public class A { 

    public B b; 
    public String bKey; 

    @Override 
    public int hashCode() { 
     final int prime = 31; 
     int result = 1; 
     result = prime * result + ((b == null) ? 0 : b.hashCode()); 
     result = prime * result + ((bKey == null) ? 0 : bKey.hashCode()); 
     return result; 
    } 
    @Override 
    public boolean equals(Object obj) { 
     if (this == obj) 
      return true; 
     if (obj == null) 
      return false; 
     if (!(obj instanceof A)) 
      return false; 
     A other = (A) obj; 
     if (b == null) { 
      if (other.b != null) 
       return false; 
     } else if (!b.equals(other.b)) 
      return false; 
     if (bKey == null) { 
      if (other.bKey != null) 
       return false; 
     } else if (!bKey.equals(other.bKey)) 
      return false; 
     return true; 
    } 
} 

public class B { 

    public A a; 
    public String aKey; 

    @Override 
    public int hashCode() { 
     final int prime = 31; 
     int result = 1; 
     result = prime * result + ((a == null) ? 0 : a.hashCode()); 
     result = prime * result + ((aKey == null) ? 0 : aKey.hashCode()); 
     return result; 
    } 
    @Override 
    public boolean equals(Object obj) { 
     if (this == obj) 
      return true; 
     if (obj == null) 
      return false; 
     if (!(obj instanceof B)) 
      return false; 
     B other = (B) obj; 
     if (a == null) { 
      if (other.a != null) 
       return false; 
     } else if (!a.equals(other.a)) 
      return false; 
     if (aKey == null) { 
      if (other.aKey != null) 
       return false; 
     } else if (!aKey.equals(other.aKey)) 
      return false; 
     return true; 
    } 
} 

Проблема возникла, когда я попытался чтобы добавить класс А к HashSet следующим образом:

public static void main(String[] args) { 

     A a = new A(); 
     B b = new B(); 
     a.b = b; 
     b.a = a; 

     Set<A> aSet = new HashSet<A>(); 
     aSet.add(a); 
    } 

Это закончится в StackOverflowError, так как при добавлении a к aSet приведет к a «методе HashCode с называют, что приведет к b» ы hashCode быть который будет r esult в ahashCode, вызываемый и т. д. и т. д. и т. д.Единственный способ обойти это - либо удалить ссылку на A от B's hashCode и equals ИЛИ включить только String bKey в метод hashCode B. Поскольку я хотел, чтобы метод B.equals включал ссылку A в проверку равенства, единственное, что я мог сделать, это сделать только то, что было использовано в B.equals, но только B.bKey в B.hashCode. Я не видел другого пути.

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

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