Почему мне нужно переопределить методы equals и hashCode в Java?


383

Недавно я прочитал этот документ Developer Works .

Документ посвящен определению, hashCode()а также equals()эффективному и правильному определению, однако я не могу понять, почему мы должны переопределить эти два метода.

Как я могу принять решение для эффективной реализации этих методов?


4
Есть две замечательные статьи по программированию. Путеводитель объясняет именно это: Когда мне следует переопределить equals? и почему вы всегда должны переопределять hashCode при переопределении equals . (Внимание, принятый ответ на самом деле неправильный.)
aioobe

Переопределение регистра только равно: два одинаковых объекта будут иметь разные хэш-коды = одни и те же объекты идут в разных сегментах (дублирование). Case Override only hashcode: два одинаковых объекта будут иметь одинаковый hashcode = один и тот же объект помещается в одну корзину (дублирование).
VdeX

Ответы:


524

Джошуа Блох говорит об эффективной Java

Вы должны переопределить hashCode () в каждом классе, который переопределяет equals (). Невыполнение этого требования приведет к нарушению общего контракта для Object.hashCode (), что помешает правильной работе вашего класса в сочетании со всеми коллекциями на основе хешей, включая HashMap, HashSet и Hashtable.

Давайте попробуем понять это на примере того, что произойдет, если мы переопределим equals()без переопределения hashCode()и попытаемся использовать a Map.

Скажем, у нас есть такой класс, и что два объекта MyClassравны, если они importantFieldравны (с помощью hashCode()и equals()сгенерированы затмением)

public class MyClass {

    private final String importantField;
    private final String anotherField;

    public MyClass(final String equalField, final String anotherField) {
        this.importantField = equalField;
        this.anotherField = anotherField;
    }

    public String getEqualField() {
        return importantField;
    }

    public String getAnotherField() {
        return anotherField;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((importantField == null) ? 0 : importantField.hashCode());
        return result;
    }

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

}

Только переопределить equals

Если только equalsпереопределено, то при myMap.put(first,someValue)первом вызове будет хеш для некоторого сегмента, а при вызове - myMap.put(second,someOtherValue)хеш для другого сегмента (так как у них другой hashCode). Таким образом, хотя они равны, поскольку они не хешируют одно и то же ведро, карта не может этого понять, и они оба остаются на карте.


Хотя нет необходимости переопределять, equals()если мы переопределяем hashCode(), давайте посмотрим, что произойдет в этом конкретном случае, когда мы знаем, что два объекта MyClassравны, если они importantFieldравны, но мы не переопределяем equals().

Только переопределить hashCode

Представь, что у тебя есть это

MyClass first = new MyClass("a","first");
MyClass second = new MyClass("a","second");

Если вы переопределяете только hashCodeто, что при вызове myMap.put(first,someValue)он занимает первое место, вычисляет его hashCodeи сохраняет в заданном сегменте. Затем при вызове myMap.put(second,someOtherValue)его следует заменить первым на второе, как указано в документации к карте, потому что они равны (согласно бизнес-требованиям).

Но проблема в том, что функция equals не была переопределена, поэтому, когда карта хэширует secondи выполняет итерации по сегменту, проверяя, есть ли такой объект k, который second.equals(k)является истинным, он не найдет ни одного, как second.equals(first)будет false.

Надеюсь, это было ясно


5
не могли бы вы пояснить немного подробнее, во втором случае, почему второй объект должен идти в другом ведре?
Хуссейн Ахтар Вахид 'Гуари'

57
Мне не нравится этот ответ, потому что он предполагает, что вы не можете переопределить hashCode () без переопределения equals (), что просто неверно. Вы говорите, что ваш пример кода (часть «override only hashCode») не будет работать, потому что вы определяете два своих объекта как равные, но - извините - это определение только у вас в голове. В первом примере у вас есть два неравных объекта с одинаковым hashCode, и это совершенно законно. Таким образом, причина, по которой вам нужно переопределить equals (), не в том, что вы уже переопределили hashCode (), а в том, что вы хотите переместить определение «равно» из своей головы в код.
user2543253 30.12.14

11
if you think you need to override one, then you need to override both of themнеправильно. Вы должны переопределить, hashCodeесли ваш класс переопределяет, equalsно обратное неверно.
akhil_mittal

4
Я думаю, что вполне нормально переопределять только hashCode () без переопределения equals (). Это также написано в Effective Java : books.google.fr/…
Джонни,

2
@PhantomReference, обратите внимание, что только переопределение equalsбудет нарушать контракт, изложенный в javadoc Object: «Если два объекта равны в соответствии с equals(Object)методом, то вызов hashCodeметода для каждого из двух объектов должен привести к одному и тому же целочисленному результату». Конечно, не все части контрактов выполняются во всем коде, но, формально говоря, это нарушение, и я бы посчитал это ошибкой, ожидающей своего появления.
августа

264

Коллекции, такие как HashMapи HashSetиспользуют значение хеш-кода объекта, чтобы определить, как он должен храниться в коллекции, и хеш-код используется снова, чтобы найти объект в его коллекции.

Хеширование поиска состоит из двух этапов:

  1. Найдите правильное ведро (используя hashCode())
  2. Ищите ведро для правильного элемента (используя equals())

Вот небольшой пример того, почему мы должны переопределить equals()и hashcode().

Рассмотрим Employeeкласс с двумя полями: возраст и имя.

public class Employee {

    String name;
    int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;
        if (!(obj instanceof Employee))
            return false;
        Employee employee = (Employee) obj;
        return employee.getAge() == this.getAge()
                && employee.getName() == this.getName();
    }

    // commented    
    /*  @Override
        public int hashCode() {
            int result=17;
            result=31*result+age;
            result=31*result+(name!=null ? name.hashCode():0);
            return result;
        }
     */
}

Теперь создайте класс, вставьте Employeeобъект в HashSetи проверьте, присутствует ли этот объект или нет.

public class ClientTest {
    public static void main(String[] args) {
        Employee employee = new Employee("rajeev", 24);
        Employee employee1 = new Employee("rajeev", 25);
        Employee employee2 = new Employee("rajeev", 24);

        HashSet<Employee> employees = new HashSet<Employee>();
        employees.add(employee);
        System.out.println(employees.contains(employee2));
        System.out.println("employee.hashCode():  " + employee.hashCode()
        + "  employee2.hashCode():" + employee2.hashCode());
    }
}

Будет напечатано следующее:

false
employee.hashCode():  321755204  employee2.hashCode():375890482

Теперь раскомментируйте hashcode()метод, выполните то же самое, и результат будет:

true
employee.hashCode():  -938387308  employee2.hashCode():-938387308

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


4
Прекрасный пример. Четко продемонстрировал разницу!
coderpc

3
приятно объяснил @rajeev
VdeX

2
@VikasVerma равно объект будет иметь одинаковый хэш-код не означает, что неравный объект будет иметь неравный хэш-код. Что если объекты на самом деле разные, но их хэш-код одинаков?
Рави

1
очень хорошо объяснил :)
Рахул

4
гораздо лучше ответ, чем принятый ответ! Спасибо
коряги

50

Вы должны переопределить hashCode () в каждом классе, который переопределяет equals (). Невыполнение этого требования приведет к нарушению общего контракта для Object.hashCode (), что помешает правильной работе вашего класса в сочетании со всеми коллекциями на основе хешей, включая HashMap, HashSet и Hashtable.


   из Эффективной Явы , Джошуа Блох

Определив equals()и hashCode()последовательно, вы можете улучшить удобство использования ваших классов в качестве ключей в основанных на хэше коллекциях. Как объясняет документ API для hashCode: «Этот метод поддерживается для использования хеш-таблиц, таких как предоставляемые» java.util.Hashtable.

Лучший ответ на ваш вопрос о том, как эффективно реализовать эти методы, - это прочитать главу 3 « Эффективной Java» .


4
Это правильный ответ. Разумеется, если вы никогда не используете класс в коллекции, основанной на хешах, то не имеет значения, что вы не реализовали hashCode().
худенький

1
В более сложных случаях вы никогда не узнаете, используют ли используемые вами коллекции хэши, поэтому держитесь подальше от «не важно, что вы не реализовали hashCode ()»
Виктор Сергиенко

1
Могу ли я переопределить hashCode () без переопределения equals ()?
Джонни

@StasS, да, вопреки тому, что говорит принятый ответ. См. Объяснение во второй части этой статьи: почему вы всегда должны переопределять hashCode, если переопределение равно
aioobe

22

Проще говоря, метод equals в Object проверяет равенство ссылок, когда два экземпляра вашего класса могут быть семантически равными, когда свойства равны. Это, например, важно, когда вы помещаете ваши объекты в контейнер, который использует equals и hashcode, такие как HashMap и Set . Допустим, у нас есть такой класс:

public class Foo {
    String id;
    String whatevs;

    Foo(String id, String whatevs) {
        this.id = id;
        this.whatevs = whatevs;
    }
}

Мы создаем два экземпляра с одинаковым идентификатором :

Foo a = new Foo("id", "something");
Foo b = new Foo("id", "something else");

Без переопределения равных мы получаем:

  • a.equals (b) ложно, потому что это два разных случая
  • a.equals (a) верно, так как это тот же экземпляр
  • b.equals (b) верно, так как это тот же экземпляр

Правильный? Ну, может быть, если это то, что вы хотите. Но допустим, что мы хотим, чтобы объекты с одинаковым идентификатором были одним и тем же объектом, независимо от того, являются ли они двумя разными экземплярами. Мы переопределяем равенства (и хэш-код):

public class Foo {
    String id;
    String whatevs;

    Foo(String id, String whatevs) {
        this.id = id;
        this.whatevs = whatevs;
    }

    @Override
    public boolean equals(Object other) {
        if (other instanceof Foo) {
            return ((Foo)other).id.equals(this.id);   
        }
    }

    @Override
    public int hashCode() {
        return this.id.hashCode();
    }
}

Что касается реализации равенства и хэш-кода, я могу порекомендовать использовать вспомогательные методы Guava.


20

Личность не равенство.

  • оператор равенства == тождеству теста .
  • equals(Object obj) метод сравнивает тест на равенство (т.е. нам нужно определить равенство, переопределив метод)

Почему мне нужно переопределить методы equals и hashCode в Java?

Сначала мы должны понять использование метода равных.

Чтобы идентифицировать различия между двумя объектами, нам нужно переопределить метод equals.

Например:

Customer customer1=new Customer("peter");
Customer customer2=customer1;
customer1.equals(customer2); // returns true by JVM. i.e. both are refering same Object
------------------------------
Customer customer1=new Customer("peter");
Customer customer2=new Customer("peter");
customer1.equals(customer2); //return false by JVM i.e. we have two different peter customers.

------------------------------
Now I have overriden Customer class equals method as follows:
 @Override
    public boolean equals(Object obj) {
        if (this == obj)   // it checks references
            return true;
        if (obj == null) // checks null
            return false;
        if (getClass() != obj.getClass()) // both object are instances of same class or not
            return false;
        Customer other = (Customer) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name)) // it again using bulit in String object equals to identify the difference 
            return false;
        return true; 
    }
Customer customer1=new Customer("peter");
Customer customer2=new Customer("peter");
Insteady identify the Object equality by JVM, we can do it by overring equals method.
customer1.equals(customer2);  // returns true by our own logic

Теперь метод hashCode может легко понять.

hashCode создает целое число для хранения объекта в структурах данных, таких как HashMap , HashSet .

Предположим, у нас есть метод переопределения равно Customerкак выше,

customer1.equals(customer2);  // returns true by our own logic

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

  • неравные экземпляры могут иметь одинаковый хэш-код.
  • равные экземпляры должны возвращать тот же хеш-код.

3
Это то, что я искал с прошлого часа. Удивительный приятель (у)
Аднан

13

Хорошо, позвольте мне объяснить концепцию очень простыми словами.

Во-первых, в более широком плане у нас есть коллекции, и hashmap является одной из структур данных в коллекциях.

Чтобы понять, почему мы должны переопределить метод equals и hashcode, нужно сначала понять, что такое hashmap и что делает.

Хэш-карта - это структура данных, которая хранит пары ключевых значений данных в виде массива. Скажем, [], где каждый элемент в 'a' является парой ключ-значение.

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

Теперь, почему используется hashmap? Если нам нужно искать среди большого массива, тогда поиск по каждому из них, если они не будут эффективными, так что метод хэша говорит нам, что позволяет предварительно обрабатывать массив с некоторой логикой и группировать элементы на основе этой логики, т.е. хеширования

Например: у нас есть массив 1,2,3,4,5,6,7,8,9,10,11, и мы применяем хеш-функцию mod 10, поэтому 1,11 будут сгруппированы вместе. Поэтому, если бы нам пришлось искать 11 в предыдущем массиве, нам пришлось бы выполнять итерацию всего массива, но когда мы группируем его, мы ограничиваем область итерации, тем самым повышая скорость. Эту структуру данных, используемую для хранения всей вышеупомянутой информации, для простоты можно рассматривать как двумерный массив.

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

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

поэтому в hashmap есть метод, называемый как put (K, V), и согласно hashmap он должен следовать приведенным выше правилам эффективного распределения массива и не добавлять дубликаты.

так что дело в том, что он сначала сгенерирует хеш-код для данного ключа, чтобы решить, в какой индекс должно входить значение. Если в этом индексе ничего нет, тогда новое значение будет добавлено туда, если что-то там уже присутствует затем новое значение должно быть добавлено после конца связанного списка в этом индексе. но помните, что дубликаты не должны добавляться в соответствии с желаемым поведением хэш-карты. Допустим, у вас есть два объекта Integer aa = 11, bb = 11. Так как каждый объект является производным от класса объекта, реализация по умолчанию для сравнения двух объектов состоит в том, что он сравнивает ссылку, а не значения внутри объекта. Таким образом, в вышеприведенном случае оба, хотя и семантически равные, не пройдут проверку на равенство, и вероятность того, что два объекта с одинаковым хеш-кодом и одинаковыми значениями будут существовать, создаст дубликаты. Если мы переопределим, мы могли бы избежать добавления дубликатов. Вы также можете сослаться наДетальная работа

import java.util.HashMap;


public class Employee {

String name;
String mobile;
public Employee(String name,String mobile) {
    this.name=name;
    this.mobile=mobile;
}

@Override
public int hashCode() {
    System.out.println("calling hascode method of Employee");
    String str=this.name;
    Integer sum=0;
    for(int i=0;i<str.length();i++){
        sum=sum+str.charAt(i);
    }
    return sum;

}
@Override
public boolean equals(Object obj) {
    // TODO Auto-generated method stub
    System.out.println("calling equals method of Employee");
    Employee emp=(Employee)obj;
    if(this.mobile.equalsIgnoreCase(emp.mobile)){

        System.out.println("returning true");
        return true;
    }else{
        System.out.println("returning false");
        return false;
    }


}

public static void main(String[] args) {
    // TODO Auto-generated method stub

    Employee emp=new Employee("abc", "hhh");
    Employee emp2=new Employee("abc", "hhh");
    HashMap<Employee, Employee> h=new HashMap<>();
    //for (int i=0;i<5;i++){
        h.put(emp, emp);
        h.put(emp2, emp2);

    //}

    System.out.println("----------------");
    System.out.println("size of hashmap: "+h.size());


}

}

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

Хеш-карта @VikasVerma не заменяет никакое значение, если хеш-код объекта равен, он только определяет индекс, в который должен быть помещен вновь добавленный объект в хеш-карту. Теперь в индексе могут быть объекты, поэтому, чтобы избежать дублирования, мы переопределяем метод equals и пишем логику для определения, когда два сравниваемых объекта должны рассматриваться как равные. Если не переопределить, тогда, хотя объекты, имеющие одинаковые значения, будут сохранены, потому что ссылки на оба объекта будут разными
Четан

11

hashCode() :

Если вы переопределите только метод хеш-кода, ничего не произойдет. Потому что он всегда возвращает новый hashCodeдля каждого объекта как класс объекта.

equals() :

Если вы переопределяете только метод равным, значение a.equals(b)true, это означает, что hashCodea и b должны быть одинаковыми, но их не должно быть. Потому что вы не переопределяете hashCodeметод.

Примечание: hashCode()метод класса Object всегда возвращает новый hashCodeдля каждого объекта.

Поэтому, когда вам нужно использовать ваш объект в коллекции на основе хеширования, необходимо переопределить equals()и hashCode().


Это интересный момент, о переопределении только hashCode () . Это совершенно нормально, верно? Или могут быть и проблемные случаи?
Джонни

1
Это вводящий в заблуждение и неправильный ответ. Переопределение (= only =) hashCode () гарантирует, что каждый объект, для которого создается экземпляр соответствующего класса с похожими свойствами, имеет одинаковый хэш-код. Но не будет полезен, поскольку ни один из них не будет равен друг другу.
mfaisalhyder

8

Java ставит правило, которое

«Если два объекта равны при использовании метода Object класса equals, то метод хэш-кода должен дать одинаковое значение для этих двух объектов».

Итак, если в нашем классе мы переопределим, equals()мы должны переопределить hashcode()метод также, чтобы следовать этому правилу. Оба метода, equals()и hashcode(), используются Hashtable, например, для хранения значений в виде пар ключ-значение. Если мы переопределим одно, а не другое, есть вероятность, что он Hashtableможет работать не так, как мы хотим, если мы используем такой объект в качестве ключа.


6

Потому что, если вы не переопределите их, вы будете использовать имплантацию по умолчанию в Object.

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


6

Чтобы использовать наши собственные объекты класса в качестве ключей в коллекциях, таких как HashMap, Hashtable и т. Д., Мы должны переопределить оба метода (hashCode () и equals ()), имея представление о внутренней работе коллекции. В противном случае это приведет к неверным результатам, которых мы не ожидаем.


6

Добавление к ответу @Lombo

Когда вам нужно будет переопределить equals ()?

Реализация по умолчанию Object's equals ()

public boolean equals(Object obj) {
        return (this == obj);
}

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

Но вы можете рассмотреть два объекта одинаково, если они имеют одинаковое значение для одного или нескольких своих свойств (см. Пример, приведенный в ответе @Lombo).

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

Я успешно реализовал equals (), и он работает отлично. Так почему же они просят переопределить hashCode ()?

Хорошо. Пока вы не используете коллекции, основанные на хеше, в вашем пользовательском классе, это нормально. Но когда-нибудь в будущем вы захотите использовать HashMapили, HashSetи если вы не сделаете overrideи «правильно реализуете» hashCode () , эта коллекция на основе хэша не будет работать так, как задумано.

Переопределить только равно (дополнение к ответу @Lombo)

myMap.put(first,someValue)
myMap.contains(second); --> But it should be the same since the key are the same.But returns false!!! How?

Прежде всего, HashMap проверяет , является ли хэш - код из secondтакой же , как first. Только если значения совпадают, он будет проверять равенство в том же сегменте.

Но здесь hashCode отличается для этих двух объектов (потому что они имеют разные адреса памяти - от реализации по умолчанию). Следовательно, он даже не будет заботиться о равенстве.

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

Почему мы не можем проверить HashMap на равенство во всех сегментах? Поэтому мне не нужно переопределять hashCode () !!

Тогда вы упускаете смысл коллекций, основанных на хеше. Учтите следующее:

Your hashCode() implementation : intObject%9.

Ниже приведены ключи, хранящиеся в виде ведер.

Bucket 1 : 1,10,19,... (in thousands)
Bucket 2 : 2,20,29...
Bucket 3 : 3,21,30,...
...

Скажем, вы хотите знать, содержит ли карта ключ 10. Хотели бы вы искать все корзины? или вы хотите искать только одно ведро?

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


5
class A {
    int i;
    // Hashing Algorithm
    if even number return 0 else return 1
    // Equals Algorithm,
    if i = this.i return true else false
}
  • put ('key', 'value') вычислит значение хеша, используя его hashCode()для определения сегмента, и используетequals() метод, чтобы ли это значение в сегменте. Если нет, он будет добавлен, в противном случае он будет заменен текущим значением.
  • get ('key') будет использовать hashCode()сначала equals()для поиска Entry (bucket) и для поиска значения в Entry

если оба переопределены,

Карта < >

Map.Entry 1 --> 1,3,5,...
Map.Entry 2 --> 2,4,6,...

если равно не переопределено

Карта < >

Map.Entry 1 --> 1,3,5,...,1,3,5,... // Duplicate values as equals not overridden
Map.Entry 2 --> 2,4,6,...,2,4,..

Если hashCode не переопределен

Карта < >

Map.Entry 1 --> 1
Map.Entry 2 --> 2
Map.Entry 3 --> 3
Map.Entry 4 --> 1
Map.Entry 5 --> 2
Map.Entry 6 --> 3 // Same values are Stored in different hasCodes violates Contract 1
So on...

HashCode Равный Контракт

  1. Два ключа, равные по одинаковому методу, должны генерировать одинаковый hashCode
  2. Два ключа, генерирующие одинаковый хэш-код, не обязательно должны быть равными (в приведенном выше примере все четные числа генерируют одинаковый хэш-код)

4

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

Для тенниса - желтый, красный. Для крикета - белый

Теперь в ведре есть шарики трех цветов: желтый, красный и белый. И что теперь вы сделали раскраску? Только вы знаете, какой цвет для какой игры.

Окраска шаров - хеширование. Выбор мяча для игры - Равно.

Если вы сделали раскраску, и кто-то выбрал мяч для игры в крикет или теннис, они не будут против цвета !!!


4

Я искал объяснение: «Если вы переопределяете только hashCode, то при вызове myMap.put(first,someValue)он берет сначала, вычисляет его hashCode и сохраняет его в данном сегменте. Затем, когда вы вызываетеmyMap.put(first,someOtherValue) его следует заменить первым на второй в соответствии с документацией карты, потому что они равны (согласно нашему определению). " :

Я думаю, что во второй раз, когда мы добавляем, myMapэто должен быть «второй» объект, какmyMap.put(second,someOtherValue)


4

1) Распространенная ошибка показана в примере ниже.

public class Car {

    private String color;

    public Car(String color) {
        this.color = color;
    }

    public boolean equals(Object obj) {
        if(obj==null) return false;
        if (!(obj instanceof Car))
            return false;   
        if (obj == this)
            return true;
        return this.color.equals(((Car) obj).color);
    }

    public static void main(String[] args) {
        Car a1 = new Car("green");
        Car a2 = new Car("red");

        //hashMap stores Car type and its quantity
        HashMap<Car, Integer> m = new HashMap<Car, Integer>();
        m.put(a1, 10);
        m.put(a2, 20);
        System.out.println(m.get(new Car("green")));
    }
}

Зеленый Автомобиль не найден

2. Проблема, вызванная hashCode ()

Проблема вызвана не переопределенным методом hashCode(). Договор между equals()и hashCode()является:

  1. Если два объекта равны, то они должны иметь одинаковый хэш-код.
  2. Если два объекта имеют одинаковый хэш-код, они могут быть равны или не совпадать.

    public int hashCode(){  
      return this.color.hashCode(); 
    }

4

Это полезно при использовании объектов значения . Ниже приводится выдержка из репозитория шаблонов Portland :

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

Поэтому у меня может быть несколько копий объекта, представляющего дату 16 января 1998 года. Любая из этих копий будет равна друг другу. Для небольших объектов, таких как этот, часто проще создавать новые и перемещать их, чем полагаться на один объект для представления даты.

Объект значения всегда должен переопределять .equals () в Java (или = в Smalltalk). (Не забудьте переопределить .hashCode ().)


3

Предположим, у вас есть класс (A), который объединяет два других (B) (C), и вам нужно хранить экземпляры (A) внутри хеш-таблицы. Реализация по умолчанию позволяет различать только экземпляры, но не по (B) и (C). Таким образом, два экземпляра A могут быть равны, но по умолчанию не позволит вам сравнить их правильно.


3

Методы equals и hashcode определены в классе объекта. По умолчанию, если метод equals возвращает true, тогда система пойдет дальше и проверит значение хеш-кода. Если хеш-код двух объектов также одинаков только тогда, объекты будут считаться одинаковыми. Таким образом, если вы переопределяете только метод equals, тогда, хотя переопределенный метод equals указывает 2 объекта, которые должны быть равны, определенный системой хэш-код может не указывать, что 2 объекта равны. Поэтому нам также необходимо переопределить хеш-код.


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

Если у одного было два объекта X и Y, чьи «равные» методы указали, что они совпадают, но хеш-код X был четным числом, а хеш-код Y был нечетным числом, как описано выше, как коллекция, в которой отмечалось, что хеш-код объекта Y был нечетным и сохранялся он во втором списке не сможет найти совпадение для объекта X. Он будет наблюдать, что хэш-код X был четным, а поскольку во втором списке нет объектов с хэш-кодами с четными номерами, он не будет беспокоить искать там что-то, что соответствует X, даже если Y будет соответствовать X. Что вы должны сказать ...
суперкат

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

3

Методы Equals и Hashcode в Java

Это методы класса java.lang.Object, который является суперклассом всех классов (в том числе пользовательских классов и других, определенных в java API).

Реализация:

public boolean equals (Object obj)

public int hashCode ()

введите описание изображения здесь

public boolean equals (Object obj)

Этот метод просто проверяет, ссылаются ли две ссылки на объекты на x и y на один и тот же объект. т.е. проверяет, если х == у.

Это рефлексивно: для любого ссылочного значения x, x.equals (x) должен возвращать true.

Это симметрично: для любых ссылочных значений x и y x.equals (y) должен возвращать true тогда и только тогда, когда y.equals (x) возвращает true.

Это транзитивно: для любых ссылочных значений x, y и z, если x.equals (y) возвращает true и y.equals (z) возвращает true, тогда x.equals (z) должен возвращать true.

Это согласуется: для любых ссылочных значений x и y множественные вызовы x.equals (y) последовательно возвращают true или последовательно возвращают false при условии, что никакая информация, используемая в сравнениях сравнения на объекте, не изменяется.

Для любого ненулевого ссылочного значения x, x.equals (null) должен возвращать false.

public int hashCode ()

Этот метод возвращает значение хеш-кода для объекта, для которого этот метод вызывается. Этот метод возвращает значение хэш-кода в виде целого числа и поддерживается для использования классов коллекции на основе хеширования, таких как Hashtable, HashMap, HashSet и т. Д. Этот метод должен быть переопределен в каждом классе, который переопределяет метод equals.

Генеральный договор hashCode:

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

Это целое число не должно оставаться согласованным при выполнении одного приложения другим исполнением того же приложения.

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

Не требуется, чтобы, если два объекта были неравны в соответствии с методом equals (java.lang.Object), то вызов метода hashCode для каждого из двух объектов должен давать разные целочисленные результаты. Тем не менее, программист должен знать, что выдача различных целочисленных результатов для неравных объектов может улучшить производительность хеш-таблиц.

Равные объекты должны генерировать один и тот же хэш-код, если они равны, однако неравные объекты не должны создавать различные хэш-коды.

Ресурсы:

Javaranch

Рисунок


Картинка (ссылка на видео) находится в приватном режиме. Сделайте это публичным, чтобы смотреть.
УдайКиран Пулипати

2

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

Ниже приведен упрощенный код, который определяет порядок людей. Person используется в качестве ключа в хеш-таблице.

public class Person {
    String name;
    int age;
    String socialSecurityNumber;

    public Person(String name, int age, String socialSecurityNumber) {
        this.name = name;
        this.age = age;
        this.socialSecurityNumber = socialSecurityNumber;
    }

    @Override
    public boolean equals(Object p) {
        //Person is same if social security number is same

        if ((p instanceof Person) && this.socialSecurityNumber.equals(((Person) p).socialSecurityNumber)) {
            return true;
        } else {
            return false;
        }

    }

    @Override
    public int hashCode() {        //I am using a hashing function in String.java instead of writing my own.
        return socialSecurityNumber.hashCode();
    }
}


public class Order {
    String[]  items;

    public void insertOrder(String[]  items)
    {
        this.items=items;
    }

}



import java.util.Hashtable;

public class Main {

    public static void main(String[] args) {

       Person p1=new Person("Tom",32,"548-56-4412");
        Person p2=new Person("Jerry",60,"456-74-4125");
        Person p3=new Person("Sherry",38,"418-55-1235");

        Order order1=new Order();
        order1.insertOrder(new String[]{"mouse","car charger"});

        Order order2=new Order();
        order2.insertOrder(new String[]{"Multi vitamin"});

        Order order3=new Order();
        order3.insertOrder(new String[]{"handbag", "iPod"});

        Hashtable<Person,Order> hashtable=new Hashtable<Person,Order>();
        hashtable.put(p1,order1);
        hashtable.put(p2,order2);
        hashtable.put(p3,order3);

       //The line below will fail if Person class does not override hashCode()
       Order tomOrder= hashtable.get(new Person("Tom", 32, "548-56-4412"));
        for(String item:tomOrder.items)
        {
            System.out.println(item);
        }
    }
}

2

Класс String и классы-оболочки имеют различную реализацию equals()иhashCode() методы, чем класс Object. Метод equals () класса Object сравнивает ссылки объектов, а не их содержимое. Метод hashCode () класса Object возвращает отдельный хеш-код для каждого отдельного объекта, независимо от того, является ли содержимое одинаковым.

Это приводит к проблемам, когда вы используете коллекцию карт, а ключ имеет тип Persistent, StringBuffer / тип компоновщика. Поскольку они не переопределяют equals () и hashCode () в отличие от класса String, equals () вернет false, когда вы сравниваете два разных объекта, даже если оба имеют одинаковое содержимое. Это сделает хэш-карту, хранящую те же ключи содержимого. Хранение одних и тех же ключей содержимого означает, что оно нарушает правило Map, поскольку Map вообще не допускает дублирование ключей. Поэтому вы переопределяете методы equals (), а также hashCode () в своем классе и предоставляете реализацию (IDE может генерировать эти методы), чтобы они работали так же, как String equals () и hashCode (), и предотвращали одинаковые ключи содержимого.

Вы должны переопределить метод hashCode () вместе с equals (), потому что equals () работает в соответствии с hashcode.

Более того, переопределение метода hashCode () вместе с equals () помогает исправить контракт equals () - hashCode (): «Если два объекта равны, то они должны иметь одинаковый хэш-код».

Когда вам нужно написать собственную реализацию для hashCode ()?

Как известно, внутренняя работа HashMap основана на принципе хеширования. Есть определенные области, где хранятся наборы записей. Вы настраиваете реализацию hashCode () в соответствии с вашими требованиями, чтобы объекты одной категории могли быть сохранены в одном индексе. когда вы сохраняете значения в коллекцию Map с использованием put(k,v)метода, внутренняя реализация put () выглядит так:

put(k, v){
hash(k);
index=hash & (n-1);
}

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

Это оно!


1

hashCode()Метод используется для получения уникального целого числа для данного объекта. Это целое число используется для определения местоположения сегмента, когда этот объект должен быть сохранен в некоторой HashTable, HashMapнапример, структуре данных. По умолчанию hashCode()метод Object возвращает целочисленное представление адреса памяти, где хранится объект.

hashCode()Метод объектов используется , когда мы вставляем их в HashTable, HashMapили HashSet. Больше оHashTables Wikipedia.org для справки.

Чтобы вставить любую запись в структуру данных карты, нам нужны ключ и значение. Если и ключ, и значения являются определяемыми пользователем типами данных, hashCode()ключ будет определять, где хранить объект внутри. Когда требуется поиск объекта на карте, хеш-код ключа будет определять, где искать объект.

Внутренний хеш-код указывает только на определенную «область» (или список, область и т. Д.). Поскольку разные ключевые объекты потенциально могут иметь один и тот же хеш-код, сам хеш-код не является гарантией того, что правильный ключ найден. Затем HashTableон выполняет итерацию этой области (все ключи с одинаковым хеш-кодом) и использует метод ключа, equals()чтобы найти правильный ключ. Как только правильный ключ найден, объект, сохраненный для этого ключа, возвращается.

Итак, как мы видим, сочетание этих hashCode()и equals()методов используется при хранении и при поиске объектов в HashTable.

НОТЫ:

  1. Всегда используйте одни и те же атрибуты объекта для генерации hashCode()и то и equals()другое. Как и в нашем случае, мы использовали идентификатор сотрудника.

  2. equals() должен быть непротиворечивым (если объекты не изменены, то он должен продолжать возвращать одно и то же значение).

  3. Всякий раз a.equals(b), тогда a.hashCode()должно быть так же, как b.hashCode().

  4. Если вы переопределяете одно, то вы должны переопределить другое.

http://parameshk.blogspot.in/2014/10/examples-of-comparable-comporator.html


hashCode()не используется для возврата уникального целого числа для каждого объекта. Это невозможно. Вы сами опровергли это во втором предложении четвертого абзаца.
Маркиз Лорн

@EJP, в большинстве случаев hascode () возвращает уникальное целое число для двух разных объектов. Но есть вероятность столкновения hascode для двух разных объектов, эта концепция называется Hashcode Collision . Пожалуйста, обратитесь: tech.queryhome.com/96931/…
Парамеш Корракути

1

ИМХО, это согласно правилу гласит: «Если два объекта равны, то они должны иметь одинаковый хэш, т. Е. Равные объекты должны создавать одинаковые значения хеша.

Учитывая выше, по умолчанию equals () в Object is ==, который выполняет сравнение по адресу, hashCode () возвращает адрес в целочисленном виде (хэш по фактическому адресу), который снова различен для отдельного объекта.

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


1

Чтобы помочь вам найти дубликаты Объектов, нам нужен пользовательский метод equals и hashCode.

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


1

Если вы хотите сохранить и извлечь свой пользовательский объект в качестве ключа в Map, тогда вы всегда должны переопределять equals и hashCode в своем пользовательском объекте. Например:

Person p1 = new Person("A",23);
Person p2 = new Person("A",23);
HashMap map = new HashMap();
map.put(p1,"value 1");
map.put(p2,"value 2");

Здесь p1 и p2 будут рассматриваться как только один объект, а mapразмер будет только 1, потому что они равны.


1
public class Employee {

    private int empId;
    private String empName;

    public Employee(int empId, String empName) {
        super();
        this.empId = empId;
        this.empName = empName;
    }

    public int getEmpId() {
        return empId;
    }

    public void setEmpId(int empId) {
        this.empId = empId;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName;
    }

    @Override
    public String toString() {
        return "Employee [empId=" + empId + ", empName=" + empName + "]";
    }

    @Override
    public int hashCode() {
        return empId + empName.hashCode();
    }

    @Override
    public boolean equals(Object obj) {

        if (this == obj) {
            return true;
        }
        if (!(this instanceof Employee)) {
            return false;
        }
        Employee emp = (Employee) obj;
        return this.getEmpId() == emp.getEmpId() && this.getEmpName().equals(emp.getEmpName());
    }

}

Тестовый класс

public class Test {

    public static void main(String[] args) {
        Employee emp1 = new Employee(101,"Manash");
        Employee emp2 = new Employee(101,"Manash");
        Employee emp3 = new Employee(103,"Ranjan");
        System.out.println(emp1.hashCode());
        System.out.println(emp2.hashCode());
        System.out.println(emp1.equals(emp2));
        System.out.println(emp1.equals(emp3));
    }

}

В Object Class equals (Object obj) используется для сравнения адреса, поэтому, когда в классе Test вы сравниваете два объекта, метод equals дает false, но когда мы переопределяем hashcode (), он может сравнивать содержимое и давать правильный результат.


и тестовый класс, который я добавил в программу ниже.
Манаш Ранджан Дакуа

В Object Class equals (Object obj) используется для сравнения адресов, поэтому, когда в классе Test вы сравниваете два объекта, метод equals дает false, но когда мы переопределяем hashcode (), он может сравнивать содержимое и давать правильный результат.
Манаш Ранджан Дакуа

1
Вы можете использовать ссылку редактирования чуть ниже этого ответа, чтобы добавить к своему ответу. Пожалуйста, не добавляйте ответ в виде двух неполных
Сурадж Рао

1

Если вы переопределяете equals()и нет hashcode(), вы не найдете никаких проблем, если только вы или кто-то другой не используете этот тип класса в хешированной коллекции, например HashSet. Люди до меня ясно объяснили документированную теорию несколько раз, я просто здесь, чтобы привести очень простой пример.

Рассмотрим класс, для которого equals()нужно что-то настраивать:

    public class Rishav {

        private String rshv;

        public Rishav(String rshv) {
            this.rshv = rshv;
        }

        /**
        * @return the rshv
        */
        public String getRshv() {
            return rshv;
        }

        /**
        * @param rshv the rshv to set
        */
        public void setRshv(String rshv) {
            this.rshv = rshv;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Rishav) {
                obj = (Rishav) obj;
                if (this.rshv.equals(((Rishav) obj).getRshv())) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }

        @Override
        public int hashCode() {
            return rshv.hashCode();
        }

    }

Теперь рассмотрим этот основной класс:

    import java.util.HashSet;
    import java.util.Set;

    public class TestRishav {

        public static void main(String[] args) {
            Rishav rA = new Rishav("rishav");
            Rishav rB = new Rishav("rishav");
            System.out.println(rA.equals(rB));
            System.out.println("-----------------------------------");

            Set<Rishav> hashed = new HashSet<>();
            hashed.add(rA);
            System.out.println(hashed.contains(rB));
            System.out.println("-----------------------------------");

            hashed.add(rB);
            System.out.println(hashed.size());
        }

    }

Это даст следующий результат:

    true
    -----------------------------------
    true
    -----------------------------------
    1

Я доволен результатами. Но если я не переопределю hashCode(), это вызовет кошмар, так как объекты Rishavс одинаковым содержимым больше не будут обрабатываться как уникальные, так как hashCodeбудут отличаться, как генерируется поведением по умолчанию, вот что будет выводиться:

    true
    -----------------------------------
    false
    -----------------------------------
    2

0

Оба метода определены в классе Object. И оба в своей простейшей реализации. Поэтому, когда вам нужно, вы захотите добавить еще реализацию к этим методам, тогда у вас есть переопределение в вашем классе.

Для Ex: метод equals () в объекте проверяет его равенство только по ссылке. Поэтому, если вам нужно сравнить его состояние, вы можете переопределить его, как это делается в классе String.


-3

Бах - «Вы должны переопределить hashCode () в каждом классе, который переопределяет equals ()».

[Из Эффективной Явы, Джошуа Блох?]

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

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

Но в любом случае, я думаю, что «правило» написано задом наперед. А пока я буду избегать использования «равно» для методов проверки на равенство :-(

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.