Неправильно ли определять хеш-код объекта как сумму, умножение, хеш-коды всех переменных класса? - PullRequest
9 голосов
/ 29 апреля 2010

Допустим, у меня есть следующий класс:

class ABC {
    private int myInt = 1;
    private double myDouble = 2;
    private String myString = "123";
    private SomeRandomClass1 myRandomClass1 = new ...
    private SomeRandomClass2 myRandomClass2 = new ...

    //pseudo code
    public int myHashCode() {
        return 37 *
               myInt.hashcode() *
               myDouble.hashCode() *
               ... *
               myRandomClass.hashcode()
    }
}

Будет ли это правильной реализацией hashCode? Обычно я так не делаю (я стараюсь следовать рекомендациям Effective Java), но у меня всегда есть соблазн сделать что-то вроде приведенного выше кода.

Спасибо

Ответы [ 4 ]

13 голосов
/ 29 апреля 2010

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

Вот цитата из Effective Java 2nd Edition , пункт 9: Всегда переопределять hashCode при переопределении equals

Несмотря на то, что рецепт в этом элементе дает достаточно хорошие хеш-функции, он не дает современных хеш-функций, а библиотеки на платформе Java не предоставляют такие хеш-функции, как в выпуске 1.6. Написание таких хеш-функций - это тема исследования, которую лучше оставить математикам и ученым. [... Тем не менее,] методы, описанные в этом пункте, должны подходить для большинства приложений.

Может не потребоваться много математических возможностей, чтобы оценить, насколько хороша ваша предложенная хеш-функция, но зачем вообще беспокоиться? Почему бы просто не следовать тому, что на практике оказалось адекватным?

рецепт Джоша Блоха

  • Сохраните некоторое постоянное ненулевое значение, скажем, 17, в переменной int, которая называется result.
  • Вычислить int хеш-код c для каждого поля:
    • Если поле имеет значение boolean, вычислить (f ? 1 : 0)
    • Если поле имеет значение byte, char, short, int, вычислить (int) f
    • Если поле имеет значение long, вычислить (int) (f ^ (f >>> 32))
    • Если поле имеет значение float, вычислить Float.floatToIntBits(f)
    • Если поле имеет значение double, вычислить Double.doubleToLongBits(f), а затем хэшировать результирующее long, как указано выше.
    • Если поле является ссылкой на объект и метод этого класса equals сравнивает поле, рекурсивно вызывая equals, рекурсивно вызывайте hashCode для поля. Если значение поля null, вернуть 0.
    • Если поле является массивом, обрабатывайте его так, как если бы каждый элемент был отдельным полем. Если каждый элемент в поле массива является значимым, вы можете использовать один из методов Arrays.hashCode, добавленных в выпуске 1.5.
  • Объедините хэш-код c в result следующим образом: result = 31 * result + c;

Теперь, конечно, этот рецепт довольно сложный, но, к счастью, вам не нужно каждый раз переопределять его, благодаря java.util.Arrays.hashCode(Object[])com.google.common.base.Objects обеспечивает удобный вариант vararg).

@Override public int hashCode() {
    return Arrays.hashCode(new Object[] {
           myInt,    //auto-boxed
           myDouble, //auto-boxed
           myRandomClass,
    });
}

Смотри также

  • Object.hashCode()

    не требуется, чтобы, если два объекта были неравны в соответствии с методом equals(java.lang.Object), то вызов метода hashCode для каждого из этих двух объектов должен давать различные целочисленные результаты. Однако, программист должен знать, что выдача различных целочисленных результатов для неравных объектов может улучшить производительность хеш-таблиц.

2 голосов
/ 29 апреля 2010

Подобные вещи разрешены договором. Но так всегда возвращается 1. В HotSpot есть флаг времени компиляции, чтобы всегда возвращать 1 для хеша идентификации. Однако такой выбор приведет к снижению производительности.

Существует определенная проблема с умножением. Мало того, что значение 0 хеша от компонента уничтожит значение, но степени два будут постепенно обнулять младшие биты.

Коммутативные операторы имеют проблему, заключающуюся в том, что перестановки значений вызовут конфликт.

Если между хеш-значениями компонентов есть определенная связь, то сложение будет особенно плохим. Например, (4, 6) и (2, 8).

1 голос
/ 29 апреля 2010

Нет, но на практике это почти наверняка не очень хорошая идея. Самое главное, вам не разрешено изменять какие-либо поля, которые вы используете в хэш-коде. Все они должны быть постоянными.

Если вы измените один из них, это может произойти: вы вставляете объект в HashSet, изменяете поля и затем проверяете, находится ли объект в HashSet. Несмотря на то, что хэш-код изменился, HashSet не найдет его!

0 голосов
/ 29 апреля 2010

Мне кажется, что если вы не можете гарантировать, что продукт является простым числом, вы можете столкнуться (хотя, вероятно, редко) между результирующими хеш-кодами для объекта

...