1. Введение
Хэш-код — это числовое представление содержимого объекта.
В Java есть несколько различных методов, которые мы можем использовать для получения хэш-кода для объекта:
Объект.hashCode()
Objects.hashCode() —
введено в Java 7 .Objects.hash()
— введено в Java 7 .
В этом уроке мы рассмотрим каждый из этих методов. Во-первых, мы начнем с определений и основных примеров. После того, как мы разберем основное использование, мы углубимся в различия между ними и возможные последствия этих различий.
2. Основное использование
2.1. Объект.hashCode()
Мы можем использовать метод Object.hashCode()
для получения хэш-кода объекта. Он очень похож на Objects.hashCode()
, за исключением того, что мы не можем его использовать, если наш объект имеет значение null
.
С учетом сказанного давайте вызовем Object.hashCode()
для двух идентичных объектов Double :
Double valueOne = Double.valueOf(1.0012);
Double valueTwo = Double.valueOf(1.0012);
int hashCode1 = valueOne.hashCode();
int hashCode2 = valueTwo.hashCode();
assertEquals(hashCode1, hashCode2);
Как и ожидалось, мы получаем одинаковые хэш-коды.
Напротив, теперь давайте вызовем Object.hashCode()
для нулевого
объекта, ожидая, что будет выброшено исключение NullPointerException
:
Double value = null;
value.hashCode();
2.2. Объекты.hashCode()
Objects.hashCode()
— это нулевой безопасный метод, который мы можем использовать для получения хэш -кода объекта. Хэш-коды необходимы для хэш-таблиц и правильной реализации equals()
.
Общий контракт для хэш-кода, как указано в JavaDoc :
- Чтобы возвращаемое целое число было одинаковым каждый раз, когда оно вызывается для неизменного объекта во время одного и того же выполнения приложения.
- Для двух объектов, которые равны в соответствии с их методом
equals()
, возвращайте один и тот же хэш-код.
Хотя это и не является обязательным требованием, разные объекты по возможности возвращают разные хэш-коды.
Во-первых, давайте вызовем Objects.hashCode()
для двух одинаковых строк:
String stringOne = "test";
String stringTwo = "test";
int hashCode1 = Objects.hashCode(stringOne);
int hashCode2 = Objects.hashCode(stringTwo);
assertEquals(hashCode1, hashCode2);
Теперь мы ожидаем, что возвращенные хэш-коды будут идентичными.
С другой стороны, если мы укажем null
для Objects.hashCode()
, мы получим ноль:
String nullString = null;
int hashCode = Objects.hashCode(nullString);
assertEquals(0, hashCode);
2.3. Объекты.хэш()
В отличие от Objects.hashCode(),
который принимает только один объект, Objects.hash()
может принимать один или несколько объектов и предоставлять для них хэш-код. Под капотом метод hash()
работает, помещая предоставленные объекты в массив и вызывая для них Arrays.hashCode()
. Если мы предоставим методу Objects.hash()
только один объект , мы не можем ожидать тех же результатов, что и при вызове Objects.hashCode()
для объекта.
Во-первых, давайте вызовем Objects.hash()
с двумя парами одинаковых строк:
String strOne = "one";
String strTwo = "two";
String strOne2 = "one";
String strTwo2 = "two";
int hashCode1 = Objects.hash(strOne, strTwo);
int hashCode2 = Objects.hash(strOne2, strTwo2);
assertEquals(hashCode1, hashCode2);
Далее давайте вызовем Objects.hash()
и Objects.hashCode()
с одной строкой:
String testString = "test string";
int hashCode1 = Objects.hash(testString);
int hashCode2 = Objects.hashCode(testString);
assertNotEquals(hashCode1, hashCode2);
Как и ожидалось, два возвращенных хэш-кода не совпадают.
3. Ключевые отличия
В предыдущем разделе мы рассмотрели ключевое различие между Objects.hash()
и Objects.hashCode()
. Теперь давайте углубимся в это немного глубже, чтобы мы могли понять некоторые разветвления.
Если нам нужно переопределить один из методов equals()
нашего класса , очень важно, чтобы мы также правильно переопределили hashCode()
.
Начнем с создания простого класса Player для нашего примера:
public class Player {
private String firstName;
private String lastName;
private String position;
// Standard getters/setters
}
3.1. Реализация хэш-кода с несколькими полями
Давайте представим, что наш класс Player
считается уникальным по всем трем полям: firstName
, lastName
и position
.
С учетом сказанного давайте посмотрим, как мы могли бы реализовать Player.hashCode()
до Java 7:
@Override
public int hashCode() {
int result = 17;
result = 31 * result + firstName != null ? firstName.hashCode() : 0;
result = 31 * result + lastName != null ? lastName.hashCode() : 0;
result = 31 * result + position != null ? position.hashCode() : 0;
return result;
}
Поскольку и Objects.hashCode()
, и Objects.hash()
были представлены в Java 7, мы должны явно проверять значение null
перед вызовом Object.hashCode()
для каждого поля.
Давайте подтвердим, что мы можем дважды вызвать hashCode()
для одного и того же объекта и получить один и тот же результат, и что мы можем вызвать его для идентичных объектов и получить тот же результат:
Player player = new Player("Eduardo", "Rodriguez", "Pitcher");
Player indenticalPlayer = new Player("Eduardo", "Rodriguez", "Pitcher");
int hashCode1 = player.hashCode();
int hashCode2 = player.hashCode();
int hashCode3 = indenticalPlayer.hashCode();
assertEquals(hashCode1, hashCode2);
assertEquals(hashCode1, hashCode3);
Далее давайте посмотрим, как мы можем немного сократить это, воспользовавшись нулевой безопасностью, которую мы получаем с помощью Objects.hashCode()
:
int result = 17;
result = 31 * result + Objects.hashCode(firstName);
result = 31 * result + Objects.hashCode(lastName);
result = 31 * result + Objects.hashCode(position);
return result;
Если мы запустим один и тот же модульный тест, мы должны ожидать таких же результатов.
Поскольку наш класс использует несколько полей для определения равенства, давайте сделаем еще один шаг и воспользуемся Objects.hash()
, чтобы сделать наш метод hashCode()
очень кратким:
return Objects.hash(firstName, lastName, position);
После этого обновления мы сможем снова успешно запустить наш модульный тест.
3.2. Objects.hash()
Подробности
Под капотом, когда мы вызываем Objects.hash(),
значения помещаются в массив, а затем для массива вызывается Arrays.hashCode()
.
С учетом сказанного давайте создадим Player
и сравним его хэш-код с Arrays.hashCode()
с используемыми нами значениями:
@Test
public void whenCallingHashCodeAndArraysHashCode_thenSameHashCodeReturned() {
Player player = new Player("Bobby", "Dalbec", "First Base");
int hashcode1 = player.hashCode();
String[] playerInfo = {"Bobby", "Dalbec", "First Base"};
int hashcode2 = Arrays.hashCode(playerInfo);
assertEquals(hashcode1, hashcode2);
}
Мы создали Player
, а затем создали String[].
Затем мы вызвали hashCode()
в Player
и использовали Arrays.hashCode()
в массиве и получили тот же хэш-код.
4. Вывод
В этой статье мы узнали, как и когда использовать Object.hashCode()
, Objects.hashCode()
и Objects.hash()
. Кроме того, мы рассмотрели различия между ними.
В качестве обзора давайте быстро подведем итоги их использования:
Object.hashCode()
: используйте для получения хэш-кода одного ненулевого объекта.Objects.hashCode()
: используйте для получения хэш-кода одного объекта, который может быть нулевым.Objects.hash()
: используйте для получения хэш-кода для нескольких объектов.
Как всегда, код примера доступен на GitHub .