Tuesday, June 29, 2010

Перегрузка equals в Java

На днях проходил собеседование в одной компании, на котором попался вопрос про перегрузку equals в Java. Оказалось, что я был не в курсе довольно элементарного, в то же время важного правила: перегрузил equals - перегрузи и hashCode. В переводе это сводится к след. набору простых правил:
  • R1. если класс перегружает equals, то класс должен перегрузить и hashCode
  • R2. когда оба equals и hashCode перегружены, они должны использовать одинаковый набор свойств класса
  • R3. если оба объекта equals, то и значения их hashCode должны совпадать
  • R4. если объект не изменяется то hashCode является первым кандидатом на ленивую инициализацию и кеширование
  • R5. существует известное заблуждение, что hashCode предоставляет уникальный идентификатор для каждого объекта. Это не так.

На stackoverflow.com нашел алгоритм для вычисления hashCode, который считается наиболее эффективным (могу только предположить, что он дает более или менее равномерный разброс значений). Почти для всех классов приемлимо разумной является реализация, предложенная в 8-м разделе книги Джоша Блоха "Эффективная Java". Также эта реализация хороша тем, что автор в своей книге приводит объяснение почему данный подход приемлем.

Сам подход описывается следующим набором правил:

A1. Создать переменную result целого типа и присвоить ей ненулевое значение.
A2. Для каждого поля, которое используется при сравнении в equals, хеш-код k вычисляется след.образом:
  • если поле f имеет тип boolean, то исп.: k = f? 0:1;
  • если поле f имеет тип byte, short или int: k=(int)f;
  • если поле f имеет тип long, то: k = (int)(f ^ (f >>> 32)), где >>> - беззнаковый сдвиг;
  • если поле f имеет тип float, то: k = Float.floatToIntBits(f);
  • если поле f имеет тип double, то: Double.floatToIntBits(f), а полученное значение уже обрабатывается как long;
  • если поле f - объект, используйте в качестве результата его k = hashCode(), если f == null, то k = 0;
  • если поле f - массив, то рассматривается каждый объект как отдельный элемент и хеш вычисляется рекурсивно с комбинированием значений так, как показано в A3.
A3. Вычислить результат с использованием полученного значения k:

result = 37 * result + k;

A4. Вернуть результат. Возвращенный результат будет иметь подходящее
распределение значений хеша в большинстве случаев.