Объекты Java равны, хотя хэш-код отличается

Читая о equals() и hashcode(), я узнал, что если два объекта равны, то их хэш-коды должны быть равны, но не наоборот.

Но приведенный ниже пример не отражает этого.

class Employee{

  private String name;

  Employee(String name){
    this.name = name;
  }

  @Override
  public boolean equals(Object obj) {           
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Employee other = (Employee) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

}

Теперь, если я создам два объекта Employee как

Employee e1 = new Employee("hi");
Employee e2 = new Employee("hi");

Если я это сделаю, e1.equals(e2), он вернет true, хотя их хэш-коды различаются, что видно из печати, e1.hashcode() и e2.hashcode().

Кто-нибудь может мне объяснить?


person Anand    schedule 13.10.2012    source источник
comment
Контракт на equals и hashCode должен поддерживаться программистом; вы не переопределили hashCode так, чтобы он вел себя нужным образом. По умолчанию используется реализация в Object, что приводит к хэш-коду идентификации.   -  person obataku    schedule 13.10.2012
comment
Просто потому, что вы проверяете неправильную реализацию   -  person Elbek    schedule 13.10.2012


Ответы (4)


Вам нужно переопределить метод hashcode и предоставить реализацию, которая находится в контракте с equals.

   @Override
    public int hashCode() {
        return name == null ? 0 : name.hashCode();
    }
  • если класс переопределяет equals, он должен переопределять hashCode
  • когда они оба переопределены, equals и hashCode должны use the same set of fields
  • если два объекта равны equal, то их значения hashCode также должны быть equal
  • если объект immutable, то hashCode является кандидатом на кэширование, а lazy initialization

Вы можете прочитать о реализации хэш-кода здесь

Если вы не переопределите метод, поведение по умолчанию будет использоваться из Объект класс.

Насколько это целесообразно, метод hashCode, определенный классом Object, действительно возвращает разные целые числа для разных объектов. (Обычно это реализуется путем преобразования внутреннего адреса объекта в целое число, но этот метод реализации не требуется для языка программирования JavaTM.)

Коллекции [HashMap,HashSet,Hashtable,LinkedHashSet,WeakHashMap] на основе хеша будут использовать hashCode() для поиска/сохранения объектов в корзинах, а затем будут вызывать equals().

person Amit Deshpande    schedule 13.10.2012
comment
привет, вы знаете, следует ли ArrayList.contains контракту? Будет ли он проверять хэш перед проверкой равенства? - person Sam YC; 02.11.2012
comment
@GMsoF ArrayList содержит метод, не использующий хэш-код(). он проверяет равенство только на основе equals(). - person Amit Deshpande; 02.11.2012

Это связано с тем, что каждый раз, когда вы переопределяете метод equals, вы также должны переопределять метод hashcode.

В противном случае ваши объекты будут сравниваться в соответствии с вашим кодом, но их HashCode будет рассчитываться в соответствии с заранее определенным алгоритмом в классе Object.

ПРИМЕЧАНИЕ: – В общем, все параметры, которые вы учитывали при проверке, равны ли ваши objects или нет, вы должны использовать all those parameters для расчета hashcodes для каждого object.

См. этот очень хороший пост, в котором описывается использование методов equals и hashcode.

Процитирую строку из этого поста, которую я уже описал выше: -

Используйте тот же набор полей для вычисления hashcode, который вы использовали в методе equals

Давайте посмотрим на приведенную ниже демонстрацию, чтобы понять приведенное выше утверждение: -

public class Demo {
    private String str;
    private String name;

    public boolean equals(Object obj) {
        // Suppose you compare two objects based on length of their name variable

        // If name equals, object are equal
        if (obj instanceof Demo) {
            return this.name.equals(((Demo)obj).name);
        }
        return false;
    }

    // ****** Badly Overrided hashcode *******
    public int hashcode() {
        // But you calculate hashcode using both the fields

        // You should never use this kind of code in hashcodes. 
        // Use complex algorithm which gives you distinct result for 
        // objects that are not equal.
        return str.length + name.length;
    }
}

Таким образом, если два объекта имеют одинаковые name, то они будут равны, но, тем не менее, если их поле str имеет разные length, то их hashcodes будут разными.

Вот почему вы всегда должны использовать same fields в расчетах equals и hashcode.

person Rohit Jain    schedule 13.10.2012
comment
Если я могу сравнить два объекта, используя равенство, как показано в приведенном выше примере, почему я должен переопределять метод хэш-кода? - person Anand; 13.10.2012
comment
Поскольку коллекции на основе хэшей (такие как HashMap и HashSet) используют hashCode, чтобы сначала выбрать корзину, а затем сравнить аргумент с каждым элементом в корзине. Вот что делает их такими эффективными: даже если карта содержит 10000 элементов, благодаря хэш-коду карта будет сравнивать аргумент только с 0, 1 или 2 другими объектами, которые находятся в том же сегменте. - person JB Nizet; 13.10.2012
comment
@ананд. Смотрите мою правку. Метод хэш-кода класса Object назначает хэш-коды на основе некоторого алгоритма, который он также использует для метода equals. Таким образом, если вы изменяете один алгоритм, вы должны соответственно изменить другой, чтобы они работали по контракту. - person Rohit Jain; 13.10.2012
comment
@АмитД. Вот почему я написал там, Badly Overrided HashCode. - person Rohit Jain; 13.10.2012
comment
лучше использовать getClass, чем instanceof в методе equals - person Anand; 13.10.2012
comment
@ананд. getClass возвращает тип reference, а не тип instance. Здесь вам нужно использовать instanceof, который проверяет фактический экземпляр. - person Rohit Jain; 13.10.2012
comment
@RohitJain: нет. getClass() является полиморфным методом и возвращает фактический класс объекта. - person JB Nizet; 13.10.2012
comment
@JBNizet О. Тогда есть ли веская причина, по которой мы должны использовать instanceof или getClass?? - person Rohit Jain; 13.10.2012
comment
Чтобы заставить метод equals соблюдать контракт A.equals(B) iff B.equals(A). Используя instanceof, вы можете иметь A instanceof B и !(B instanceof A). Хотя это спорная проблема. Подробнее об этом читайте в stackoverflow.com/questions/596462/. - person JB Nizet; 13.10.2012
comment
@JBNizet Спасибо :) Я читал Effective Java. И только что добрались до этой части. - person Rohit Jain; 13.10.2012
comment
@JBNizet chrs mate - у меня есть еще два вопроса, основанные на вашем ответе: (1) если кто-то не использует коллекцию на основе хэша, нужно ли все же переопределять метод хэш-кода, (2) является ли List коллекцией на основе хэша? друг. - person BKSpurgeon; 25.11.2016
comment
@BKSpurgeon 1. да, потому что, если класс не является закрытым, и вы не напортачите, ничто не мешает позже использовать его в коллекции на основе хэша, даже если сейчас это не так. Зачем делать хрупкий код, если его легко сделать надежным? 2. List - это интерфейс, поэтому он зависит от реализации, но я не знаю (и не могу представить) какую-либо реализацию List, использующую hashCode. - person JB Nizet; 25.11.2016

Вам также потребуется переопределить hashCode, чтобы получить ожидаемое поведение. Реализация Object.hashCode по умолчанию, вероятно, возвращает ссылки на объекты, хотя согласно документы это не требуется.

Без переопределения hashCode вы не можете ожидать специализированных результатов; это аналогично переопределению equals.

person pb2q    schedule 13.10.2012

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

person Tushar Paliwal    schedule 13.10.2012