- PVSM.RU - https://www.pvsm.ru -
-i-equals().png)
Я недавно начал заниматься программированием, и в этой области для меня много нового. Данная статья рассчитана на начинающих java-программистов, и, надеюсь, поможет в освоении излюбленной темы для собеседований “hashCode и equals”.
Хочу сразу предостеречь, что я не являюсь экспертом в данной теме и могу что-то не так понимать, поэтому, если вы нашли ошибку или неточность — свяжитесь со мной.
Если очень просто, то хеш-код — это число. На самом деле просто, не так ли? Если более точно, то это битовая строка фиксированной длины, полученная из массива произвольной длины (википедия [1]).
Пример №1
Выполним следующий код:
public class Main {
public static void main(String[] args) {
Object object = new Object();
int hCode;
hCode = object.hashCode();
System.out.println(hCode);
}
}
В результате выполнения программы в консоль выведется целое 10-ти значное число. Это число и есть наша битовая строка фиксированной длины. В java она представлена в виде числа примитивного типа int, который равен 4-м байтам, и может помещать числа от -2 147 483 648 до 2 147 483 647. На данном этапе важно понимать, что хеш-код это число, у которого есть свой предел, который для java ограничен примитивным целочисленным типом int.
Вторая часть объяснения гласит:
полученная из массива произвольной длины.
Под массивом произвольной длины мы будем понимать объект. В 1 примере в качестве массива произвольной длины у нас выступает объект типа Object.
В итоге, в терминах Java, хеш-код — это целочисленный результат работы метода, которому в качестве входного параметра передан объект.
Этот метод реализован таким образом, что для одного и того-же входного объекта, хеш-код всегда будет одинаковым. Следует понимать, что множество возможных хеш-кодов ограничено примитивным типом int, а множество объектов ограничено только нашей фантазией. Отсюда следует утверждение: “Множество объектов мощнее множества хеш-кодов”. Из-за этого ограничения, вполне возможна ситуация, что хеш-коды разных объектов могут совпасть.
Здесь главное понять, что:
Ситуация, когда у разных объектов одинаковые хеш-коды называется — коллизией. Вероятность возникновения коллизии зависит от используемого алгоритма генерации хеш-кода.
Сперва, что-бы избежать путаницы, определимся с терминологией. Одинаковые объекты — это объекты одного класса с одинаковым содержимым полей.
-i-equals()-2.png)
-i-equals()-3.png)
-i-equals()-4.png)
-i-equals()-5.png)
equals()
Начнем с того, что в java, каждый вызов оператора new порождает новый объект в памяти. Для иллюстрации создадим какой-нибудь класс, пускай он будет называться “BlackBox”.
Пример №2
Выполним следующий код:
public class BlackBox {
int a;
int b;
BlackBox(int varA, int varB){
a = varA;
b = varB;
}
}
Создадим класс для демонстрации BlackBox.
public class DemoBlackBox {
public static void main(String[] args) {
BlackBox object1 = new BlackBox(5, 10);
BlackBox object2 = new BlackBox(5, 10);
}
}
Во втором примере, в памяти создастся два объекта.
-i-equals()-6.png)
Но, как вы уже обратили внимание, содержимое этих объектов одинаково, то есть эквивалентно. Для проверки эквивалентности в классе Object существует метод equals(), который сравнивает содержимое объектов и выводит значение типа boolean true, если содержимое эквивалентно, и false — если нет.
object1.equals(object2);// должно быть true, поскольку содержимое объектов эквивалентно
Эквивалентность и хеш-код тесно связанны между собой, поскольку хеш-код вычисляется на основании содержимого объекта (значения полей) и если у двух объектов одного и того же класса содержимое одинаковое, то и хеш-коды должны быть одинаковые (см. правило 2 [2]).
Иными словами:
object1.equals(object2)// должно быть true
object1.hashCode() == object2.hashCode()// должно быть true
Я написал “должно быть”, потому что если вы выполните предыдущий пример, то на самом деле результатом выполнения всех операций будет false. Для пояснения причин, заглянем в исходные коды класса Object.
Как известно, все java-классы наследуются от класса Object. В этом классе уже определены методы hashCode() и equals().
Определяя свой класс, вы автоматически наследуете все методы класса Object. И в ситуации, когда в вашем классе не переопределены (@overriding) hashCode() и equals(), то используется их реализация из Object.
Рассмотрим исходный код метода equals() в классе Object.
public boolean equals(Object obj) {
return (this == obj);
}
При сравнение объектов, операция “==” вернет true лишь в одном случае — когда ссылки указывают на один и тот-же объект. В данном случае не учитывается содержимое полей.
Выполнив приведённый ниже код, equals вернет true.
public class DemoBlackBox {
public static void main(String[] args) {
BlackBox object3 = new BlackBox(5, 10);
BlackBox object4 = object3;// Переменная object4 ссылается на
//тот-же объект что и переменная object3
object3.equals(object4)//true
}
}
-i-equals()-7.png)
Теперь понято, почему Object.equals() работает не так как нужно, ведь он сравнивает ссылки, а не содержимое объектов.
Далее на очереди hashCode(), который тоже работает не так как полагается.
Заглянем в исходный код метода hashCode() в классе Object:
public native int hashCode();
Вот собственно и вся реализация. Ключевое слово native означает, что реализация данного метода выполнена на другом языке, например на C, C++ или ассемблере. Конкретный native int hashCode() реализован на C++, вот исходники — http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/tip/src/share/vm/runtime/synchronizer.cpp [3] функция get_next_hash.
При вычислении хэш-кода для объектов класса Object по умолчанию используется Park-Miller RNG [4] алгоритм. В основу работы данного алгоритма положен генератор случайных чисел. Это означает, что при каждом запуске программы у объекта будет разный хэш-код.
Получается, что используя реализацию метода hashCode() от класса Object, мы при каждом создании объекта класса new BlackBox(), будем получать разные хеш-коды. Мало того, перезапуская программу, мы будем получать абсолютно разные значения, поскольку это просто случайное число.
Но, как мы помним, должно выполняться правило: “если у двух объектов одного и того же класса содержимое одинаковое, то и хеш-коды должны быть одинаковые ”. Поэтому, при создании пользовательского класса, принято переопределять методы hashCode() и equals() таким образом, что бы учитывались поля объекта.
Это можно сделать вручную либо воспользовавшись средствами генерации исходного кода в IDE. Например, в Eclipse это Source → Generate hashCode() and equals()...
В итоге, класс BlackBox приобретает вид:
public class BlackBox {
int a;
int b;
BlackBox(int varA, int varB){
a = varA;
b = varB;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + varA;
result = prime * result + varB;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BlackBox other = (BlackBox) obj;
if (varA != other.varA)
return false;
if (varB != other.varB)
return false;
return true;
}
}
Теперь методы hashCode() и equals() работают корректно и учитывают содержимое полей объекта:
object1.equals(object2);//true
object1.hashCode() == object2.hashCode();//true
Кому интересно переопределение в ручную, можно почитать Effective Java — Joshua Bloch [5], chapter 3, item 8,9.
Создавая пользовательский класс, нужно переопределять методы hashCode() и equals(), что бы они корректно работали и учитывали данные объекта. Кроме того, если оставить реализацию из Object, то при использовании java.util.HashMap возникнут проблемы, поскольку HashMap активно используют hashCode() и equals() в своей работе, но про это хорошо написано у tarzan82 [6] в посте Структуры данных в картинках.HashMap [7].
Автор: MaximPedich
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/26304
Ссылки в тексте:
[1] википедия: http://ru.wikipedia.org/wiki/%D0%A5%D0%B5%D1%88%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5
[2] правило 2: #2
[3] http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/tip/src/share/vm/runtime/synchronizer.cpp: http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/tip/src/share/vm/runtime/synchronizer.cpp
[4] Park-Miller RNG: http://en.wikipedia.org/wiki/Park-Miller_random_number_generator
[5] Effective Java — Joshua Bloch: http://www.amazon.com/Effective-Java-2nd-Joshua-Bloch/dp/0321356683
[6] tarzan82: http://habrahabr.ru/users/tarzan82/
[7] Структуры данных в картинках.HashMap: http://habrahabr.ru/post/128017/
[8] Исходный код класса java.lang.Object: http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/classes/java/lang/Object.java
[9] Откуда растут ноги у hashCode: http://habrahabr.ru/post/165683/
[10] Как сгенерировать hashCode в Java: http://www.seostella.com/ru/article/2012/10/04/kak-sgenerirovat-hashcode-v-java.html
[11] Эффективное и безошибочное объявление методов hashCode() и equals(): http://www.ibm.com/developerworks/ru/library/j-jtp05273/index.html
[12] Сравнение объектов: теория: http://www.skipy.ru/technics/objCompTh.html
[13] Сравнение объектов: практика: http://www.skipy.ru/technics/objCompPr.html
[14] Источник: http://habrahabr.ru/post/168195/
Нажмите здесь для печати.