Как хэш-коды для перечислений вычисляются в Java и комбинируют хэш-коды enum для ключа HashMap

У меня есть класс, который содержит разные перечисления (разные типы). Этот класс используется как ключ для HashMap. В настоящее время классы hashCode реализуются следующим образом:

  public static class Key implements Comparable<Key> {
    final int a;
    final Enum1 enum1;
    final Enum2 enum2;

    @Override
    public int hashCode() {
      return a ^ enum1.hashCode() ^ enum2.hashCode();
    }

    // ... definition of equals and toString ...
  }

Теперь, если enums hashCode просто вернет индекс значения enum в определении перечисления, это не будет оптимальным (слишком много столкновений). Определение метода для Enum.hashCode() таково:

/**
 * Returns a hash code for this enum constant.
 *
 * @return a hash code for this enum constant.
 */
public final int hashCode() {
    return super.hashCode();
}

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

PS: Конечно, вам придется использовать что-то более сложное, если один и тот же перечисление используется несколько раз в ключе.

Ответ 1

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

С другой стороны, существуют лучшие способы генерации хэш-кода с меньшей вероятностью столкновения. Проверьте, например, значения по умолчанию, которые eclipse может автогенерировать для вас (щелкните правой кнопкой мыши, Source > Generate hashCode и равно)

public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((enum1 == null) ? 0 : enum1.hashCode());
    result = prime * result + ((enum2 == null) ? 0 : enum2.hashCode());
    return result;
}

Бросив простые числа в микс (точная математика ускользает от меня), вы должны быть немного более устойчивыми.

Заметьте, вы также можете позволить eclipse генерировать метод equals для вас! (Даже toString). Не говоря о том, что вы должны слепо доверять им, но обычно это очень хорошее начало.

Ответ 2

В Java 8 для этой цели вы можете использовать Objects.hash().

Например, вы можете переписать hashCode на

//
import static java.util.Objects.hash;

// 
@Override
public int hashCode() {
  return hash(a, enum1, enum2);
}

Ответ 3

Просто протестировал это на JVM Oracle 1.6. Перечисление действительно делегирует Object.hashCode(). И он варьируется между разными тиражами. Имейте в виду, что ключи, таким образом, нестабильны между различными экземплярами виртуальных машин/виртуальных машин. Поэтому, когда вы сериализуете HashMap и читаете его на другой виртуальной машине, вы не сможете искать значения там, используя ключи, которые были созданы в этой виртуальной машине.

Ответ 4

Как сказано выше, Enum неизменяемы в Java, Таким образом, hashcode, созданный для Enum, является идеальным ключом для коллекции Hash, так же как String - идеальные ключи.

Объявление enum - это особый вид объявления класса. Тип перечисления имеет общедоступные, самонастраиваемые члены для каждой из названных констант перечисления. Все классы перечисления имеют высококачественные методы toString, hashCode и equals. Все являются Serializable, Comparable и фактически окончательными. Ни один из них не является клонированным. Все "Объектные методы", кроме ofString, являются окончательными: мы заботимся о сравнении и сериализации и гарантируем t шляпа сделана правильно.