Неверно ли определять хэш-код объекта как сумму, умножение, независимо от всех хэш-кодов переменных класса?

Скажем, у меня есть следующий класс:

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? Это не то, как я обычно это делаю (я склонен следить за эффективными линиями Java), но у меня всегда есть соблазн просто сделать что-то вроде приведенного выше кода.

Спасибо

Ответ 1

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

Вот цитата из Effective Java 2nd Edition, Item 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

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

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

У коммутативных операторов есть проблема, что перестановки значений вызовут столкновение.

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

Ответ 3

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

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

Ответ 4

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