Перейти к основному содержимому

Java Objects.hash() против Objects.hashCode()

· 6 мин. чтения

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 .