Чому мені потрібно замінити методи рівності та хеш-коди на Java?


383

Нещодавно я прочитав цей документ для розробників .

Документ все про визначення hashCode()та equals()ефективно і правильно, але я не можу зрозуміти, чому ми повинні перевизначити ці два методи.

Як я можу прийняти рішення про ефективне застосування цих методів?


4
У програмі program.guide є дві чудові статті, що пояснюють саме це: Коли я повинен переосмислити рівність? і Чому завжди слід переосмислювати хеш-код, коли перестановка дорівнює . (Попередження, прийнята відповідь насправді неправильна.)
aioobe

Case Override лише дорівнює: два ідентичні об’єкти матимуть різний хеш-код = однакові об’єкти йдуть у різному відрізку (дублювання). Case Override only hashcode: два ж об’єкти матимуть однаковий хеш-код = той самий об’єкт переходить у одне відро (дублювання).
VdeX

Відповіді:


524

Джошуа Блох говорить про Ефективну Java

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

Спробуємо розібратися в цьому на прикладі того, що станеться, якщо ми перекриємо equals()без перекреслення hashCode()та спробу використання а 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)слід замінити перше на друге згідно з Документацією на карту, оскільки вони рівні (відповідно до бізнес-вимог).

Але проблема полягає в тому, що рівняння не було переосмислено, тому коли карта хеширує secondі повторюється через відро, шукаючи, чи є такий об'єкт k, який second.equals(k)є правдою, він не знайде жодного, як second.equals(first)буде false.

Сподіваюся, це було зрозуміло


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

57
Мені ця відповідь не подобається, тому що це говорить про те, що ви не можете переотримати hashCode () без переосмислення рівнянь (), що просто не відповідає дійсності. Ви кажете, що ваш прикладний код (частина "переосмислити лише хеш-код") не буде працювати, оскільки ви визначаєте два об'єкти як рівні, але, вибачте - це визначення є лише у вашій голові. У першому прикладі у вас є два нерівні об'єкти з однаковим хеш-кодом, і це абсолютно законно. Тому причина, яку вам потрібно переосмислити, дорівнює рівнянню (), не тому, що ви вже перекрили хеш-код (), а тому, що ви хочете перенести визначення "дорівнює" з голови в код.
користувач2543253

11
if you think you need to override one, then you need to override both of themнеправильно. Вам потрібно переосмислити, hashCodeякщо ваш клас переосмислює, equalsале зворотне значення не відповідає дійсності.
akhil_mittal

4
Я думаю, що цілком нормально перекривати лише хеш-код (), не змінюючи також рівність (). Також те, що написано на Ефективній Java : books.google.fr/…
Джонні

2
@PhantomReference, зауважте, що лише перевизначення equalsбуде порушувати контракт, прописаний в javadoc Object: "Якщо два об'єкти рівні відповідно до equals(Object)методу, то виклик hashCodeметоду на кожному з двох об'єктів повинен давати однаковий цілий результат". Звичайно, не всі частини всіх договорів виконуються в усьому кодексі, але все-таки, формально кажучи, це порушення, і я вважаю це помилкою, яка чекає цього.
aioobe

263

Колекції, такі як 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об'єкт в a 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 () у кожному класі, який переосмислює рівний (). Якщо цього не зробити, це призведе до порушення загального контракту на Object.hashCode (), що перешкоджатиме нормальному функціонуванню вашого класу в поєднанні з усіма колекціями на основі хешу, включаючи HashMap, HashSet і Hashtable.


   від Ефективна Java , Джошуа Блох

Визначаючи equals()та hashCode()послідовно, ви можете покращити зручність використання своїх класів як ключів у колекціях на основі хешу. Як пояснює документ API для hashCode: "Цей метод підтримується на користь хештелів, таких як ті, які надаєjava.util.Hashtable .

Найкраща відповідь на ваше запитання щодо ефективного впровадження цих методів пропонує вам ознайомитись з розділом 3 ефективної Java .


4
Це правильна відповідь. Наслідком є, звичайно, те, що якщо ви ніколи не використовуєте клас у колекції на основі хешу, то не має значення, що ви його не реалізували hashCode().
стрункий

1
У більш складних випадках ви ніколи не знаєте, чи використовуються ваші колекції хешами, тому тримайтеся подалі від "неважливо, що ви не застосували hashCode ()"
Віктор Сергієнко

1
Чи можу я замінити хеш-код () без перевищення рівняння ()?
Джонні

@StasS, так, всупереч тому, що говорить прийнята відповідь. Дивіться пояснення у другій частині цієї статті: Чому завжди слід переосмислювати хеш-код, коли
переорієнтування

22

Простіше кажучи, метод рівнянь в «Об’єкт» перевіряє на рівність еталону, де як два екземпляри вашого класу все ще можуть бути семантично рівними, коли властивості рівні. Це, наприклад, важливо, коли ви розміщуєте об'єкти в контейнері, який використовує рівний і хеш-код, як 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();
    }
}

Щодо реалізації рівних і хеш-коду, я можу порекомендувати використовувати помічникові методи Гуави


20

Ідентичність - це не рівність.

  • дорівнює оператору == ідентичності тесту .
  • equals(Object obj) метод порівнює тест на рівність (тобто нам потрібно сказати рівність, замінивши метод)

Чому мені потрібно замінити методи рівності та хеш-коди на Java?

Спершу ми повинні зрозуміти використання методу рівних.

Для того, щоб визначити відмінності між двома об'єктами, нам потрібно переозначити метод рівних.

Наприклад:

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

Працюючи зі структурою даних, коли ми зберігаємо об’єкт у відрах (відро - це фантазійне ім'я для папки). Якщо ми використовуємо вбудовану хеш-техніку, для вище двох клієнтів вона генерує два різних хеш-коду. Таким чином, ми зберігаємо один і той же однаковий предмет у двох різних місцях. Щоб уникнути подібних питань, нам слід перекрити метод хеш-коду, виходячи з наступних принципів.

  • У неоднакових екземплярів може бути однаковий хеш-код.
  • рівні екземпляри повинні повертати один і той же хеш-код.

3
Це те, що я шукав з останніх 1 години. Awesome mate (y)
Аднан

13

Гаразд, дозвольте мені пояснити поняття дуже простими словами.

По-перше, з більш широкої точки зору ми маємо колекції, а хешмап - одна з структур даних у колекціях.

Щоб зрозуміти, чому нам потрібно перекрити і метод рівних, і хеш-код, якщо потрібно спочатку зрозуміти, що таке хешмап і що це робить.

Хешмап - це структура даних, яка зберігає ключові пари значень даних у масиві. Скажімо, [], де кожен елемент у "a" - пара ключових значень.

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

Тепер для чого використовується хешмап? Якщо нам доведеться шукати серед великого масиву, то пошук по кожному з них, якщо вони не будуть ефективними, тож те, що хеш-техніка говорить нам, що дозволяє попередньо обробити масив за допомогою якоїсь логіки та згрупувати елементи, засновані на цій логіці, тобто хешинг

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

Тепер крім вищевказаного хешмапу також вказано, що він не повинен додавати в нього жодних копій. І це головна причина, чому нам доводиться перекривати рівності та хеш-код

Отже, коли сказано, що пояснюють внутрішню роботу хешмапу, нам потрібно знайти, які методи має хешмап і як він дотримується вищевказаних правил, які я пояснив вище

тому хешмап має метод, який називається put (K, V), і відповідно до хешмапу він повинен дотримуватися наведених вище правил ефективного розподілу масиву і не додавати жодних дублікатів

тож, що робиться, це те, що він спочатку генерує хеш-код для даного ключа, щоб вирішити, в якому індексі значення повинно входити. Якщо в цьому індексі нічого немає, то нове значення буде додане там, якщо щось там вже є то нове значення слід додати після закінчення зв'язаного списку в цьому індексі. але пам’ятайте, що жодних дублікатів не слід додавати відповідно до бажаної поведінки хешмапу. тому скажемо, що у вас є два цілі об'єкти 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());


}

}

У мене є одна плутанина, чому нам потрібно перекрити метод рівних, коли ми перекриваємо метод hashCode у випадку HashMap? У будь-якому випадку, хешмап замінює значення, якщо хеш-код об'єкта дорівнює.
Вікас Верма

@VikasVerma хешмап не замінює будь-якого значення, якщо хеш-код об’єктів дорівнює, він визначає лише індекс, де потрібно розмістити щойно доданий об’єкт до хешмапу. Тепер в індексі можуть бути об'єкти, тому, щоб уникнути дублювання, ми перекриваємо метод рівних і записуємо логіку для визначення, коли два об'єкти порівняно слід вважати рівними. Якщо не буде відмінено, то хоча об’єкти, що мають однакові значення, будуть зберігатися, оскільки посилання на обидва об'єкти буде різною
Четан

11

hashCode() :

Якщо ви перекриєте лише метод хеш-коду, нічого не станеться. Тому що він завжди повертає нове hashCodeдля кожного об'єкта як клас Object.

equals() :

Якщо ви перекриваєте лише рівний метод, a.equals(b)це правда, значить, hashCodea і b повинні бути однаковими, але не відбуватися. Тому що ви не перекривалиhashCode метод.

Примітка: hashCode()метод класу Object завжди повертає нове hashCodeдля кожного об'єкта.

Отже, коли вам потрібно використовувати ваш об'єкт у колекції, заснованій на хешировании, повинен перекрити і те, equals()і hashCode().


Це цікавий момент, щодо переосмислення лише хеш-коду () . Це абсолютно добре, правда? Або можуть бути і проблемні випадки?
Джонні

1
Це хибна і неправильна відповідь. Переопределення (= only =) hashCode () гарантує, що кожен об'єкт, який інстанціюється відповідного класу з подібними властивостями, має однаковий хеш-код. Але це не буде корисним, оскільки жоден з них не буде рівним один одному.
mfaisalhyder

8

Java встановлює правило, яке

"Якщо два об'єкти рівні, використовуючи метод Object class equals, метод методу хеш-коду повинен дати однакове значення для цих двох об'єктів."

Отже, якщо в нашому класі ми перекриємо, equals()ми повинні перекрити hashcode()метод також, щоб дотримуватися цього правила. Обидва методи, equals()і hashcode(), використовуються Hashtable, наприклад, для зберігання значень як пар ключ-значення. Якщо ми перекриємо одне, а не інше, є ймовірність, що Hashtableможе не працювати так, як ми хочемо, якщо ми будемо використовувати такий об’єкт як ключ.


6

Тому що, якщо ви їх не перекриєте, ви будете використовувати реалізацію за замовчуванням в Object.

Враховуючи, що рівність екземпляра та значення коду, як правило, вимагають знання того, що складає об'єкт, їх, як правило, потрібно буде переосмислити у вашому класі, щоб мати будь-яке відчутне значення.


6

Для того, щоб використовувати власні об’єкти класу в якості ключів у колекціях, таких як HashMap, Hashtable тощо., Нам слід перекрити обидва методи (hashCode () та equals ()), маючи обізнаність щодо внутрішньої роботи колекції. Інакше це призводить до неправильних результатів, яких ми не очікуємо.


6

Додавання до відповіді @Lombo

Коли вам потрібно буде перекрити рівняння ()?

Реалізація за замовчуванням дорівнює об'єкту ()

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

що означає, що два об'єкти будуть вважатися рівними лише в тому випадку, якщо вони мають однакову адресу пам'яті, що буде істинним, лише якщо ви порівнюєте об'єкт із самим собою.

Але ви, можливо, захочете вважати два об'єкти однаковими, якщо вони мають однакове значення для однієї або декількох їх властивостей (див. Приклад, наведений у відповіді @Lombo).

Таким чином, ви переможете equals()в цих ситуаціях, і ви дасте власні умови для рівності.

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

Ну, доки ви не використовуєте колекції на основі "хеш" на визначеному користувачем класі, це добре. Але деякий час у майбутньому, можливо, ви захочете використовувати HashMapабо, HashSetякщо ви цього не зробите overrideі не "правильно реалізуєте" hashCode () , ця колекція на основі Hash не працюватиме за призначенням.

Визначити лише рівне (додавання до відповіді @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 перевіряє, чи hashCode of secondє таким же, як first. Тільки якщо значення однакові, він перейде до перевірки рівності в тому ж відрі.

Але тут hashCode відрізняється для цих 2 об’єктів (тому що вони мають різну адресу пам'яті - від реалізації за замовчуванням). Отже, навіть не буде байдуже перевірити рівність.

Якщо у вас метод перерви всередині переопределеного рівняння equals (), він не вступатиме, якщо вони мають різні хеш-коди. contains()перевірки, hashCode()і лише якщо вони однакові, він назвав би ваш equals()метод.

Чому ми не можемо зробити перевірку HashMap на рівність у всіх відрах? Тож немає необхідності перекривати хеш-код () !!

Тоді вам не вистачає точки колекцій, заснованих на хеш. Розглянемо наступне:

Your hashCode() implementation : intObject%9.

Далі наведені ключі, що зберігаються у формі відра.

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

Скажіть, ви хочете знати, чи карта містить ключ 10. Чи хочете ви пошукати всі відра? чи Ви хочете шукати лише одне відро?

Виходячи з хеш-коду, ви визначите, що якщо 10 присутнє, він повинен бути присутнім у Bucket 1. Отже, буде шукати лише Bucket 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()метод, щоб визначити, чи значення вже присутнє в Bucket. Якщо це не буде додано інше, воно буде замінено на поточне значення
  • get ('key') використовуватиме hashCode()для того, щоб спочатку знайти Entry (відро) та equals()знайти значення в 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. Два ключі, рівні за рівним методом, повинні генерувати один і той же хеш-код
  2. Дві клавіші, що генерують один і той же хеш-код, не повинні бути рівними (У наведеному вище прикладі всі парні числа генерують один і той же хеш-код)

4

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

Для тенісу - жовтий, червоний. Для крикету - білий

Зараз у відрі є кульки в трьох кольорах Жовтий, Червоний та Білий. І що тепер ви робили розфарбовування Тільки ви знаєте, який колір для якої гри.

Фарбування кульок - Хешинг. Вибір м'яча для гри - дорівнює.

Якщо ви робили забарвлення, а хтось обирає м'яч для крикету чи тенісу, вони не заперечують проти кольору !!!


4

Я розглядав пояснення "Якщо ви лише переосмислите hashCode, тоді, коли ви зателефонуєте, myMap.put(first,someValue)він займає перше, обчислює його хеш-код і зберігає його в заданому відрі. Потім, коли ви телефонуєте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

Це корисно при використанні об'єктів цінності . Далі наведено уривок із сховища портландшафтів :

Прикладами ціннісних об’єктів є такі речі, як числа, дати, грошові рядки та рядки. Зазвичай це невеликі предмети, які використовуються досить широко. Їх ідентичність базується не на їхньому стані, а не на їх об'єктній ідентичності. Таким чином, ви можете мати кілька копій одного і того ж об'єкта концептуальної цінності.

Тому я можу мати декілька копій об'єкта, що представляє дату 16 січня 1998 року. Будь-яка з цих копій буде дорівнює одна одній. Для такого маленького об'єкта, як цей, часто простіше створити нові та перемістити їх, а не покластись на один об’єкт для відображення дати.

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


3

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


3

Методи, рівні та хеш-код, визначені в об'єктному класі. За замовчуванням, якщо метод рівний повертає істину, тоді система піде далі і перевірить значення хеш-коду. Якщо хеш-код двох об'єктів також однаковий, то тоді об'єкти будуть вважатися однаковими. Отже, якщо ви переосмислюєте лише метод рівний, то, хоча метод переосмисленого рівняння вказує на те, що два об'єкти є рівними, визначений системою хеш-код може не означати, що два об'єкти рівні. Тому нам також потрібно перекрити хеш-код.


Якщо метод equals повертає значення true, не потрібно перевіряти хеш-код. Однак, якщо два об'єкти мають різні хеш-коди, слід мати можливість розцінювати їх як різні без виклику рівних. Крім того, знання того, що жодна з речей у списку не має конкретного хеш-коду, означає, що жодна з речей у списку не може співпадати най-об’єкт із цим хеш-кодом. Як простий приклад, якщо у вас є список об'єктів, хеш-коди яких - це парні числа, та перелік об'єктів, де вони є непарними числами, жоден об'єкт, чий хеш-код є парним числом, не буде у другому списку.
суперкар

Якщо в одного було два об'єкти X і Y, чиї "рівні" методи вказали, що вони збігаються, але хеш-код X був парним числом, а хеш-код Y - непарним числом, колекцією, як описано вище, в якій зазначалося, що хеш-код об'єкта Y був непарним і зберігався він у другому списку не зможе знайти збіг для об’єкта X. Він зауважив би, що хеш-код X був рівним, а оскільки у другому списку немає об'єктів з парними хеш-кодами, це не турбує шукати там щось, що відповідає X, хоча Y відповідатиме X. Що ви повинні сказати ...
supercat

... було б, що багато колекцій уникатимуть порівнювати речі, чиї хеш-коди означають, що вони не можуть бути рівними. З огляду на два об'єкти, хеш-коди яких невідомі, часто швидше порівняти їх безпосередньо, ніж обчислити їх хеш-коди, тому немає гарантії, що речі, які повідомляють про нерівні хеш-коди, але повернення trueдля equalsцього, не будуть розглядатися як відповідність. З іншого боку, якщо у колекціях трапляється помітити, що речі не можуть мати однаковий хеш-код, вони, ймовірно, не помітять, що вони рівні.
supercat

3

Дорівнює рівням і методам Hashcode на Java

Це методи класу java.lang.Object, який є суперкласом усіх класів (а також користувацькі класи та інші, визначені в API Java).

Впровадження:

загальнодоступні булеві рівні (Object obj)

public int hashCode ()

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

загальнодоступні булеві рівні (Object obj)

Цей метод просто перевіряє, чи дві посилання на об'єкт x і y посилаються на один і той же об'єкт. тобто він перевіряє, чи x == y.

Це рефлексивно: для будь-якого еталонного значення x, x.equals (x) повинно повернути істинне.

Він симетричний: для будь-яких опорних значень x і y, x.equals (y) має повертати істину тоді і лише тоді, коли y.equals (x) повертає істинне.

Він є перехідним: для будь-яких опорних значень x, y і z, якщо x.equals (y) повертає істинне, а y.equals (z) повертає істинне, то x.equals (z) має повертати істинне.

Це послідовно: для будь-яких опорних значень x і y багаторазове виклик x.equals (y) послідовно повертає істинне або послідовно повертає помилкове, за умови, що жодна інформація, що використовується в рівних порівняннях на об'єкті, не змінюється.

Для будь-якого ненульового опорного значення x, x.equals (null) має повертати false.

public int hashCode ()

Цей метод повертає значення хеш-коду для об'єкта, на який цей метод викликається. Цей метод повертає значення хеш-коду як ціле число і підтримується на користь хеш-класів колекцій колекцій, таких як Hashtable, HashMap, HashSet і т.д. Цей метод повинен бути замінений у кожному класі, який переосмислює метод рівний.

Загальним контрактом hashCode є:

Кожного разу, коли він викликається на одному і тому ж об'єкті не раз під час виконання програми Java, метод hashCode повинен послідовно повертати одне ціле ціле число, за умови, що жодна інформація, що використовується в порівнянні з об'єктом, не змінюється.

Це ціле число не повинно залишатися послідовним від одного виконання програми до іншого виконання тієї самої програми.

Якщо два об'єкти рівні за методом рівних (Object), то виклик методу hashCode на кожному з двох об'єктів повинен давати однаковий цілий результат.

Не потрібно, якщо два об'єкти неоднакові за методом рівних (java.lang.Object), то виклик методу hashCode на кожному з двох об'єктів повинен отримати чіткі цілі результати. Однак програмісту слід пам’ятати, що створення чітких цілих результатів для неоднакових об’єктів може покращити продуктивність хештелів.

Рівні об'єкти повинні створювати один і той же хеш-код до тих пір, поки вони рівні, однак неоднакові об'єкти не повинні створювати чітких хеш-кодів.

Ресурси:

JavaRanch

Картина


Зображення (відеопосилання) знаходиться в приватному режимі. Зробити це публічним для перегляду.
UdayKiran Pulipati

2

У наведеному нижче прикладі, якщо ви прокоментуєте заміщення рівня рівності або хеш-коду в класі 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

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

Це призводить до проблеми, коли ви використовуєте колекцію карт, і ключ має стійкий тип, тип StringBuffer / builder. Оскільки вони не переосмислюють рівняння () та hashCode () на відміну від класу String, то рівняння () повертається помилковим при порівнянні двох різних об'єктів, хоча обидва мають однаковий вміст. Це зробить хеш-карту для зберігання однакових ключів вмісту. Зберігання тих самих контентних ключів означає, що це порушення правила Map, оскільки Map взагалі не дозволяє повторювати ключі. Тому ви переосмислюєте як рівні (), так і методи hashCode () у вашому класі та забезпечуєте реалізацію (IDE може генерувати ці методи), щоб вони працювали так само, як String's equals () та hashCode () та запобігали тим же ключам вмісту.

Ви повинні перекрити метод hashCode () разом з equals (), тому що equals () працює відповідно до хеш-коду.

Крім того, переопределення методу 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() метод повертає і ціле представлення адреси пам'яті, де зберігається об'єкт.

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

Щоб вставити будь-який запис у структуру даних карт, нам потрібно як ключ, так і значення. Якщо і ключ, і значення визначають типи даних, то hashCode()цей ключ визначатиме, де зберігати об'єкт всередині. Коли потрібно також знайти об’єкт з карти, хеш-код ключа визначатиме, де шукати об’єкт.

Хеш-код вказує лише на певну "область" (або список, відро тощо) внутрішньо. Оскільки різні ключові об'єкти потенційно можуть мати один і той же хеш-код, сам хеш-код не є гарантією того, що потрібний ключ знайдений. HashTableПотім перебирає цю область (всі ключі з однаковим хеш - код) і використовує ключ в equals()метод , щоб знайти правильний ключ. Після виявлення правої клавіші повертається об’єкт, що зберігається для цього ключа.

Отже, як ми бачимо, комбінація методів hashCode()та equals()методів використовується при зберіганні та пошуку предметів у a 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/…
Paramesh Korrakuti

1

IMHO, це відповідно до правила, яке сказано: "Якщо два об'єкти рівні, то вони повинні мати однаковий хеш, тобто рівні об'єкти повинні створювати рівні хеш-значення.

Згадане вище, за замовчуванням у Object є ==, що робить порівняння адреси, hashCode () повертає адресу в цілому (хеш на фактичну адресу), який знову відрізняється для окремого Об'єкта.

Якщо вам потрібно використовувати користувацькі об'єкти в колекціях на основі Hash, вам потрібно перекрити обидва рівні () і hashCode (), наприклад, якщо я хочу підтримувати HashSet об'єктів Employee, якщо я не використовую більш сильний hashCode і дорівнює Я можу переставити перевагу над двома різними об'єктами працівника, це трапляється, коли я використовую вік як хеш-код (), однак я повинен використовувати унікальне значення, яке може бути ідентифікатором співробітника.


1

Щоб допомогти вам перевірити наявність дублікатів Об'єктів, нам потрібні спеціальні рівності та хеш-код.

Оскільки хеш-код завжди повертає число, його завжди швидко отримати об'єкт, використовуючи число, а не алфавітний ключ. Як це буде робити? Припустимо, ми створили новий об’єкт, передавши якесь значення, яке вже є в якомусь іншому об'єкті. Тепер новий об’єкт поверне те саме хеш-значення, що й для іншого об'єкта, оскільки передане значення те саме. Після повернення одного і того ж хеш-значення JVM буде переходити на одну і ту ж адресу пам'яті кожного разу, і якщо у випадку є більше одного об'єкта для одного і того ж хеш-значення, він використовує метод equals () для ідентифікації правильного об'єкта.


1

Коли ви хочете зберігати та отримувати свій власний об’єкт як ключ у Картах, вам слід завжди переосмислювати рівняння та хеш-код у вашому користувальницькому об’єкті. Наприклад:

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, якщо ви порівнюєте два об'єкти, то дорівнює методу, що дає помилковий, але коли ми перекриваємо хеш-код (), він може порівняти вміст і дати належний результат.


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

У Object Class equals (Object obj) використовується для порівняння адресних порівнянь. Чому тоді, коли в класі Test, якщо ви порівнюєте два об'єкти, то дорівнює методу, що дає помилковий, але коли ми перекриваємо хеш-код (), він може порівняти вміст і дати належний результат.
Манаш Ранджан Дакуа

1
ви можете скористатись посиланням для редагування трохи під цією відповіддю, щоб додати свою відповідь. Будь ласка, не додайте відповідь як дві неповні
Suraj Rao

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

Bah - "Ви повинні перекрити hashCode () у кожному класі, який переосмислює рівний ()."

[з Ефективної Java, Джошуа Блох?]

Чи не все це неправильно? Перевизначення хеш-коду, ймовірно, означає, що ви пишете клас хеш-ключа, але переопределення рівності, безумовно, не робить. Існує багато класів, які не використовуються як хеш-ключі, але хочуть метод тестування логічної рівності з іншої причини. Якщо ви вибрали для нього "рівний", вам може бути надано право написати реалізацію хеш-коду шляхом надмірного застосування цього правила. Все, що досягається, - це додавання неперевіреного коду в базу даних коду, зла, яка чекає, коли хтось подорожує в майбутньому. Також код, який вам не потрібен, є антигігійним. Це просто неправильно (і генерована ідея, ймовірно, буде несумісна з вашими рукотворними рівнями).

Зрозуміло, вони повинні мати мандат на Інтерфейс для об'єктів, записаних для використання в якості ключів? Незалежно від цього, Об'єкт ніколи не повинен був надати хеш-код () і рівний () імхо за замовчуванням. Напевно, це заохочує багато розбитих хеш-колекцій.

Але все одно, я думаю, що "правило" написане назад на фронт. Тим часом я буду уникати використання "рівних" для методів тестування рівності :-(

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.