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

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

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()
    }
}

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

Спасибо

9
задан devoured elysium 29 April 2010 в 05:52
поделиться

4 ответа

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

Вот цитата из Эффективное 2-е издание Java , пункт 9: Всегда переопределяйте hashCode , когда вы переопределяете равным

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

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

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

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

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

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

См. Также

  • Объект.hashCode ()

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

13
ответ дан 4 December 2019 в 13:00
поделиться

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

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

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

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

2
ответ дан 4 December 2019 в 13:00
поделиться

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

0
ответ дан 4 December 2019 в 13:00
поделиться

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

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

1
ответ дан 4 December 2019 в 13:00
поделиться