Понимание Java System.identityHashCode, Object.hashCode и Object.equals
1. Контракт equals()
Метод equals(Object) используется для сравнения текущего объекта с другим объектом на основе значений свойств каждого объекта. Вы можете переопределить (override) этот метод в своем классе.
public boolean equals(Object other)
Например: класс Money с 2 свойствами: currencyCode & amount (Код валюты и сумма). Два объекта Money считаются равными по методу equals(), если они имеют одинаковые currencyCode и amount:
Money.java
package org.o7planning.equals.ex;
import java.util.Objects;
public class Money {
private String currencyCode;
private int amount;
public Money(String currencyCode, int amount) {
this.amount = amount;
this.currencyCode = currencyCode;
}
public int getAmount() {
return amount;
}
public String getCurrencyCode() {
return currencyCode;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof Money)) {
return false;
}
Money o = (Money) other;
return this.amount == o.amount //
&& Objects.equals(this.currencyCode, o.currencyCode);
}
}
При переопределении (override) метода equals() необходимо соблюдать следующие критерии, они называются контрактом equals():
1 | Reflexive (Возвратный) | Объект должен равняться самому себе. |
2 | Symmetric (Симметричный) | x.equals(y) должен возвращать тот же результат, что и y.equals(x). |
3 | Transitive (Переходный) | Если x.equals(y) и y.equals(z) , то также x.equals(z). |
4 | Consistent (Непротиворечивый) | Значение x.equals(y) не изменяется, если свойства, участвующие в сравнении, не изменяются. (Случайность не допускается). |
Symmetric
Симметрия (symmetric) of equals() должна быть гарантирована, другими словами, если x.equals(y), то y.equals(x). Это звучит просто, но иногда вы нарушаете его непреднамеренно.
Например, Класс WrongVoucher ниже нарушает симметрию контракта equals():
WrongVoucher.java
package org.o7planning.equals.ex;
import java.util.Objects;
public class WrongVoucher extends Money {
private String store;
public WrongVoucher(String store, String currencyCode, int amount) {
super(currencyCode, amount);
this.store = store;
}
public String getStore() {
return store;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof WrongVoucher)) {
return false;
}
WrongVoucher o = (WrongVoucher) other;
return this.getAmount() == o.getAmount() //
&& Objects.equals(this.getCurrencyCode(), o.getCurrencyCode()) //
&& Objects.equals(this.store, o.store);
}
}
На первый взгляд, класс WrongVoucher и его метод equals() кажутся правильными. Он отлично работает, если вы сравните 2 объекта WrongVoucher друг с другом, но вы увидите проблемы, если вы сравните объект WrongVoucher с объектом Money и наоборот.
WrongVoucherTest.java
package org.o7planning.equals.ex;
public class WrongVoucherTest {
public static void main(String[] args) {
Money m = new Money("USD", 100);
WrongVoucher wv = new WrongVoucher("Chicago S1", "USD", 100);
System.out.println("m.equals(wv): " + m.equals(wv)); // true
System.out.println("wv.equals(m): " + wv.equals(m)); // false
}
}
Output:
m.equals(wv): true
wv.equals(m): false
Чтобы избежать описанной выше ловушки, мы можем переписать класс Voucher и использовать Money как свойство, а не наследование от Money.
Voucher.java
package org.o7planning.equals.ex;
import java.util.Objects;
public class Voucher {
private String store;
private Money money;
public Voucher(String store, String currencyCode, int amount) {
this.store = store;
this.money = new Money(currencyCode, amount);
}
public String getStore() {
return store;
}
public Money getMoney() {
return money;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof Voucher)) {
return false;
}
Voucher o = (Voucher) other;
return Objects.equals(this.store, o.store) //
&& this.money.equals(o.money);
}
}
2. System.identityHashCode(Object)
В Java, статический метод System.identityHashCode(obj) возвращает identity hashcode (идентификационный хэш-код) объекта obj, который является неотрицательным целым числом в диапазоне [0, 2^31-1]. Identity hashcode объекта null равен 0.
@HotSpotIntrinsicCandidate
public static native int identityHashCode(Object x);
Согласно идее дизайна, identity hashcode разных объектов должен быть разным. Однако это не гарантируется абсолютно. Алгоритм JVM может гарантировать только то, что вероятность дублирования of identity hashcode очень мала. Identity hashcode объекта вычисляется только в первый момент, когда он фактически используется и хранится в Header объекта.
Identity hashcode, безусловно, не генерируется на основе адреса объекта в памяти. К сожалению, документации по алгоритму генерации identity hashcode нет, секрет заключается в исходном коде of JVM, написанном на языке C++. Я обновлю этот алгоритм, если появится дополнительная информация.
3. Object.hashcode()
Метод hashCode() класса java.lang.Object возвращает hashcode текущего объекта, который точно является identity hashcode этого объекта
public class Object {
public int hashCode() {
return System.identityHashCode(this);
}
}
Пример о hashcode и identity hashcode истого объекта (new java.lang.Object()).
HashCodeEx1.java
package org.o7planning.hashcode.ex;
public class HashCodeEx1 {
public static void main(String[] args) {
Object obj1 = new Object();
int idHashcode = System.identityHashCode(obj1);
int hashcode = obj1.hashCode();
System.out.println("Identity Hashcode: " + idHashcode);
System.out.println("Hashcode: " + hashcode);
}
}
Output:
Identity Hashcode: 1651191114
Hashcode: 1651191114
Классы-потомки of java.lang.Object может переопределить (override) метод hashCode(), чтобы вернуть пользовательское значение, но необходимо обеспечить следующие правила, которые также называются контрактом hashCode().
1 | Equals consistency (Равная согласованность) | Если 2 объекта равны в соответствии с методом equals(Object), их метод hashCode() должен возвращать одно и то же значение. |
2 | Internal consistency (Внутренняя согласованность) | Значение hashCode() может измениться только в том случае, если изменятся свойства, участвующие в методе equals(Object). |
Два объекта, которые не равны в соответствии с методом equals(Object), не обязательно имеют разные hashcode. Однако два разных объекта с разными значениями hashcode улучшат производительность of Hash table (подробнее см. в статье о HashMap и HashSet).
См. Подробнее:
- Руководство Java HashSet
HashCodeEx2.java
package org.o7planning.hashcode.ex;
public class HashCodeEx2 {
public static void main(String[] args) {
Employee tom = new Employee("Tom");
Employee jerry = new Employee("Jerry");
System.out.println("Employee: " + tom.getFullName());
System.out.println(" - Identity hashcode: " + System.identityHashCode(tom));
System.out.println(" - Hashcode: " + tom.hashCode());
System.out.println("\nEmployee: " + jerry.getFullName());
System.out.println(" - Identity hashcode: " + System.identityHashCode(jerry));
System.out.println(" - Hashcode: " + jerry.hashCode());
}
}
class Employee {
private String fullName;
public Employee(String fullName) {
this.fullName = fullName;
}
public String getFullName() {
return this.fullName;
}
@Override
public int hashCode() {
if (this.fullName == null || this.fullName.isEmpty()) {
return 0;
}
char ch = this.fullName.charAt(0);
return (int) ch;
}
}
Output:
Employee: Tom
- Identity hashcode: 1579572132
- Hashcode: 84
Employee: Jerry
- Identity hashcode: 359023572
- Hashcode: 74
4. Нарушение согласованности hashCode() & equals()
В принципе, когда ваш класс переопределяет (override) метод equals(Object), вы также должны переопределить метод hashCode(), чтобы убедиться, что 2 объекта, которые равны по методу equals(Object), будут иметь один и тот же hashcode. Это необходимо и безопасно, когда вы используете объект этого класса в качестве ключа *HashMap (HashMap, WeakHashMap, IdentityHashMap,...).
Класс BadTeam ниже нарушает Equals consistency (равная согласованность):
BadTeam.java
package org.o7planning.equals.ex;
import java.util.Objects;
public class BadTeam {
private String name;
private int numberOfMembers;
public BadTeam(String name, int numberOfMembers) {
this.name = name;
this.numberOfMembers = numberOfMembers;
}
public String getName() {
return name;
}
public int getNumberOfMembers() {
return numberOfMembers;
}
@Override
public boolean equals(Object other) {
if(this == other) {
return true;
}
if(!(other instanceof BadTeam)) {
return false;
}
BadTeam o = (BadTeam) other;
return Objects.equals(this.name, o.name);
}
@Override
public int hashCode() {
return this.numberOfMembers;
}
}
BadTeamTest.java
package org.o7planning.equals.ex;
public class BadTeamTest {
public static void main(String[] args) {
BadTeam team1 = new BadTeam("Team 1", 3);
BadTeam team2 = new BadTeam("Team 1", 5);
boolean isEquals = team1.equals(team2); // true
int hashcode1 = team1.hashCode(); // 3
int hashcode2 = team2.hashCode(); // 5
System.out.println("team1.equals(team2): " + isEquals); // true
System.out.println("hashcode1 == hashcode2: " + (hashcode1 == hashcode2)); // false
}
}
Output:
team1.equals(team2): true
hashcode1 == hashcode2: false
Нарушение контракта hashCode() может произойти при использовании класса *HashMap(HashMap, WeakHashMap, IdentityHashMap,..). Всё может работать не так, как вы ожидаете.
HashMap_BadTeam_Test.java
package org.o7planning.equals.ex;
import java.util.HashMap;
public class HashMap_BadTeam_Test {
public static void main(String[] args) {
// BadTeam team --> String leader.
HashMap<BadTeam, String> map = new HashMap<>();
BadTeam team1 = new BadTeam("Team 1", 3);
BadTeam team2 = new BadTeam("Team 1", 5);
map.put(team1, "Tom");
map.put(team2, "Jerry");
BadTeam team = new BadTeam("Team 1", 10);
String leader = map.get(team);
System.out.println("Leader of " + team.getName() + " is " + leader);
}
}
Output:
Leader of Team 1 is null
Смотрите также, как HashMap, WeakHashMap и IdentityHashMap хранят данные, чтобы понять больше того, что было упомянуто выше:
Java Basic
- Настройте java compiler для обработки вашего Annotation (Annotation Processing Tool)
- Программирование на Java для группы с помощью Eclipse и SVN
- Руководство Java WeakReference
- Руководство Java PhantomReference
- Сжатие и декомпрессия в Java
- Настройка Eclipse для использования JDK вместо JRE
- Методы String.format() и printf() в Java
- Синтаксис и новые функции в Java 8
- Регулярные выражения Java
- Руководство Java Multithreading Programming
- Библиотеки Java JDBC Driver для различных типов баз данных
- Руководство Java JDBC
- Получить значения столбцов, автоматически возрастающих при вставлении (Insert) записи, используя JDBC
- Руководство Java Stream
- Руководство Java Functional Interface
- Введение в Raspberry Pi
- Руководство Java Predicate
- Абстрактный класс и Interface в Java
- Модификатор доступа (Access modifiers) в Java
- Руководство Java Enum
- Руководство Java Annotation
- Сравнение и Сортировка в Java
- Руководство Java String, StringBuffer и StringBuilder
- Обработка исключений Java - Java Exception Handling
- Руководство Java Generics
- Манипулирование файлами и каталогами в Java
- Руководство Java BiPredicate
- Руководство Java Consumer
- Руководство Java BiConsumer
- Что мне нужно для начала работы с Java?
- История Java и разница между Oracle JDK и OpenJDK
- Установить Java в Windows
- Установите Java в Ubuntu
- Установите OpenJDK в Ubuntu
- Установить Eclipse
- Установите Eclipse в Ubuntu
- Быстрое изучение Java для начинающих
- История бит и байтов в информатике
- Типы данных в java
- Битовые операции
- Команда if else в Java
- команды switch в Java
- Циклы в Java
- Массивы (Array) в Java
- JDK Javadoc в формате CHM
- Наследование и полиморфизм в Java
- Руководство Java Function
- Руководство Java BiFunction
- Пример Java encoding и decoding с использованием Apache Base64
- Руководство Java Reflection
- Java Удаленный вызов методов - Java RMI
- Руководство Программирование Java Socket
- Какую платформу я должен выбрать для разработки приложений Java Desktop?
- Руководство Java Commons IO
- Руководство Java Commons Email
- Руководство Java Commons Logging
- Понимание Java System.identityHashCode, Object.hashCode и Object.equals
- Руководство Java SoftReference
- Руководство Java Supplier
- Аспектно-ориентированное программирование Java с помощью AspectJ (AOP)
Show More
- Руководства Java Servlet/JSP
- Руководства Java Collections Framework
- Java API для HTML, XML
- Руководства Java IO
- Руководства Java Date Time
- Руководства Spring Boot
- Руководства Maven
- Руководства Gradle
- Руководства Java Web Services
- Руководства Java SWT
- Руководства JavaFX
- Руководства Oracle Java ADF
- Руководства Struts2 Framework
- Руководства Spring Cloud