Хэш-код объекта, hashCode

В классе Object, который является родительским классом для объектов java, определен метод hashCode(), позволяющий получить уникальный целый номер для данного объекта. Когда объект сохраняют в коллекции типа HashSet, то данный номер позволяет быстро определить его местонахождение в коллекции и извлечь. Функция hashCode() объекта Object возвращает целое число int, размер которого равен 4-м байтам и значение которого располагается в диапазоне от -2 147 483 648 до 2 147 483 647.

Рассмотрим простой пример HashCodeTest.java, который в консоли будет печатать значение hashCode.

public class HashCodeTest
{
    public static void main(String[] args)
    {
        int hCode = (new Object()).hashCode();
        System.out.println("hashCode = " + hCode);
    }
}

Значение hashCode программы можно увидеть в консоли.


hashCode = 954599881
 

По умолчанию, функция hashCode() для объекта возвращает номер ячейки памяти, где объект сохраняется. Поэтому, если изменение в код приложения не вносятся, то функция должна выдавать одно и то же значение. При незначительном изменении кода значение hashCode также изменится.

Функция сравнения объектов equals()

В родительском классе Object наряду с функцией hashCode() имеется еще и логическая функция equals(Object)/ Функция equals(Object) используется для проверки равенства двух объектов. Реализация данной функции по умолчанию просто проверяет по ссылкам два объекта на предмет их эквивалентности.

Рассмотрим пример сравнения двух однотипных объектов Test следующего вида :

class Test
{
    int f1 = -1;
    int f2 = -1;

    public Test (final int f1, final int f2)
    {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1()
    {
        return this.f1;
    }
    public int getF2()
    {
        return this.f2;
    }
}

Создадим 2 объекта типа Test с одинаковыми значениями и сравним объекты с использованием функции equals().

public class EqualsExample
{
    public static void main(String[] args)
    {
        Test obj1 = new Test(11, 12);
        Test obj2 = new Test(11, 12);
        System.out.println("Объекты :\n\tobj1 = " + obj1 + 
                                    "\n\tobj2 = " + obj2);
        System.out.println("hashCode объектов :" +
                               "\n\tobj1.hashCode = " + obj1.hashCode() + 
                               "\n\tobj2.hashCode = " + obj2.hashCode());
        System.out.println("Сравнение объектов :" +
                          "\n\tobj1.equals(obj2) = " + obj1.equals(obj2));
    }
}

Результат выполнения программы будет выведен в консоль :


Объекты :
    obj1 = example.Test@780324ff
    obj2 = example.Test@16721ee7
hashCode объектов :
    obj1.hashCode = 2013471999
    obj2.hashCode = 376577767
Сравнение объектов :
    obj1.equals(obj2) = false
 

Не трудно было догадаться, что результат сравнения двух объектов вернет «false». На самом деле, это не совсем верно, поскольку объекты идентичны и в real time application метод должен вернуть true. Чтобы достигнуть этого корректного поведения, необходимо переопределить метод equals() объекта Test.

@Override
public boolean equals(Object obj)
{
    if (this == obj)
        return true;
    else if (obj == null)
        return false;
    else if (getClass() != obj.getClass())
        return false;

    Test other = (Test) obj;
    if (f1 != other.getF1())
        return false;
    else if (f2 != other.getF2())
        return false;
    return true;
}

Вот теперь функция сравнения equals() возвращает значение «true». Достаточно ли этого? Попробуем добавить объекты в коллекцию HashSet и посмотрим, сколько объектов будет в коллекции? Для этого в в метод main примера EqualsExample добавим следующие строки :

Set<Test> objects = new HashSet<Test>();
objects.add(obj1);
objects.add(obj2);

System.out.println("Коллекция :\n\t" + objects);

Однако в коллекции у нас два объекта.


Коллекция :
	[example.Test@780324ff, example.Test@16721ee7]
 

Поскольку Set содержит только уникальные объекты, то внутри HashSet должен быть только один экземпляр. Чтобы этого достичь, объекты должны возвращать одинаковый hashCode. То есть нам необходимо переопределить также функцию hashCode() вместе с equals().

@Override
public int hashCode()
{
    final int prime = 31;
    int result = 1;
    result = prime * result + f1;
    result = prime * result + f2;
    return result;
}

Вот теперь все будет корректно выполняться - для двух объектов с одинаковыми параметрами функция equals() вернет значение «true», и в коллекцию попадет только один объект. В консоль будет выведен следующий текст работы программы :


Объекты :
	obj1 = example.Test@696
	obj2 = example.Test@696
hashCode объектов :
	obj1.hashCode = 1686
	obj2.hashCode = 1686
Сравнение объектов :
	obj1.equals(obj2) = true
Коллекция :
	[example.Test@696]
 

Таким образом, переопределяя методы hashCode() и equals() мы можем корректно управлять нашими объектами, не допуская их дублирования.

Использование библиотеки commons-lang.jar для переопределения hashCode() и equals()

Процесс формирования методов hashCode() и equals() в IDE Eclipse автоматизирован. Для создания данных методов необходимо правой клавишей мыши вызвать контекстное меню класса (кликнуть на классе) и выбрать пункт меню Source (Class >> Source), в результате которого будет открыто следующее окно со списком меню для класса.

Выбираем пункт меню «Generate hachCode() and equals()» и в класс будут добавлены соответствующие методы.

@Override
public int hashCode()
{
    final int prime = 31;
    int result = 1;
    result = prime * result + f1;
    result = prime * result + f2;
    return result;
}

@Override
public boolean equals(Object obj)
{
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;

    Test other = (Test) obj;
    if (f1 != other.f1)
        return false;
    if (f2 != other.f2)
        return false;
    return true;
}

Библиотека Apache Commons включает два вспомогательных класса HashCodeBuilder и EqualsBuilder для вызова методов hashCode() и equals(). Чтобы включить их в класс необходимо внести следующие изменения.

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;

 ...
@Override
public int hashCode()
{
    final int prime = 31;
    return new HashCodeBuilder(getF1()%2 == 0 ? 
                                        getF1() + 1 : 
                                        getF1(), prime).toHashCode();
}

@Override
public boolean equals(Object obj)
{
    if (this == obj)
        return true;
    else if (obj == null)
        return false;
    else if (getClass() != obj.getClass())
        return false;

    Test other = (Test) obj;
    return new EqualsBuilder().append(getF1(),other.getF1()).isEquals();
}

Примечание : желательно в методах hashCode() и equals() не использовать ссылки на поля, заменяйте их геттерами. Это связано с тем, что в некоторых технологиях java поля загружаются при помощи отложенной загрузки (lazy load) и не доступны, пока не вызваны их геттеры.

Скачать пример

Исходный код рассмотренного примера в виде проекта Eclipse можно скачать здесь (263 Kб).

  Рейтинг@Mail.ru