Нечетный хэш-код Java (с Lombok) Недетерминированное поведение

Я получаю некоторое поведение, которое я не понимаю с Java Hashcode (используя Lombok). У меня есть абстрактный объект Storeable для вещей, которые я храню в различных хранилищах данных.

public abstract class Storable implements Serializable {
  ...
}

@Data
@EqualsAndHashCode(of="url", callSuper=false)
@Slf4j
@ToString(of="url")
public final class Foo extends Storable {

  private URL url;

  public Foo(@NonNull URL url, ...) {

    super();
    this.url = url;
    ...
  }

  ...
}

Когда я обновляю несколько Foos с помощью new Foo(new URL("http:///www.foo.com ")) и я перебираю их и проверяю каждый foo.hashCode(), я получаю одно и то же значение. Но если я завершу программу, а затем запущу другой запуск, foos в новом запуске будут иметь другое значение hashCode, даже если они выглядят одинаково с точки зрения данных. Несоответствие причиняет мне горе, потому что я пытаюсь использовать hashCode для идентификации уникальных объектов от запуска к запуску. Возможно, что еще более странно, для заданного URL-адреса, который я использую для тестирования, я каждый раз вижу 1 из тех же 4 целых чисел.

Я что-то пропустил либо в реализации getHashcode() по умолчанию для Java, либо в реализации Lombok @EqualsAndHashCode? Или есть что-то в URL-адресе, что может привести к тому, что он будет иметь другое значение hashCode? Заранее спасибо за помощь!


person Tyson    schedule 21.07.2015    source источник
comment
Я только что заметил, что ваш пример кода url является полем String, но имеет параметр URL в конструкторе. что это?   -  person dkatzel    schedule 21.07.2015
comment
Хороший улов, это URL. Я испортил копирование/вставку, когда пытался отредактировать, так что только основные части попали в вопрос. FWIW Я пытался создавать объекты URL сами по себе и вызывать .hashCode() в разных процессах, и каждый раз получал один и тот же результат.   -  person Tyson    schedule 21.07.2015
comment
Это очень странно. То, что делает Ломбок в этом отношении, четко определено и проверено, см., например. до и после. Создайте автономный пример и зарегистрируйте проблему или опубликовать в группе.   -  person maaartinus    schedule 23.07.2015


Ответы (2)


Если вы используете Java 7, это, вероятно, использует альтернативную реализацию хэш-кода murmur, которая не гарантирует создание одного и того же хэш-кода для экземпляров JVM (или одна и та же JVM запускается несколько раз)

Статья, в которой обсуждается изменение хэш-кода в Java 7

Соответствующий раздел:

Еще пара слов об альтернативном хэш-коде:

  • он не раскрывается публично через класс String. Вы можете получить к нему доступ, используя (неофициальный) метод sun.misc.Hashing.stringHash32.

  • в отличие от исходного хеш-кода, hash32 для двух строк, содержащих одни и те же символы, но работающих на разных JVM (на одной и той же машине или на разных машинах), не обязательно будет одинаковым (на самом деле, скорее всего, это не так, поскольку Значение «HASHING_SEED» включено в расчет, который инициализируется при запуске JVM с использованием текущего времени)

  • цель альтернативного хэш-кода — повысить производительность HashMap и связанных с ним классов со строковыми ключами и предотвратить атаки типа «отказ в обслуживании» с коллизией хэшей.

  • Его использование не включено по умолчанию. Вам нужно установить свойство «jdk.map.althashing.threshold», чтобы включить его. Если вы установите для этого значение X, то HashMap и связанные классы с емкостью не менее X будут использовать альтернативный алгоритм хеширования.

Предупреждение, если вы хотите включить альтернативное хеширование: до Java 7u40 (т. е. все версии между Java 7u6 и Java 7u39) была проблема с производительностью, из-за которой создание HashMap при включенном альтернативном хешировании было медленнее, чем должно быть. Таким образом, если вы хотите включить альтернативное хеширование, убедитесь, что у вас установлена ​​последняя версия среды выполнения Java 7.

Это было добавлено в Java 7u6, но было удалено в Java 8.

Вот внутренняя реализация хэш-функции бормотания Java 7 в коде grep.

Вот ссылка на реализацию HashMap в Java 7, которая использует новый расчет хеш-кода, если ключ в карте представляет собой строку http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/java/util/HashMap.java#HashMap.hash%28java.lang.Object%29

person dkatzel    schedule 21.07.2015
comment
Вау, фантастический ответ. Спасибо! Я отлаживал это в течение нескольких часов, предполагая, что в моем коде есть какая-то проблема. :) Я попробую это с более поздней JRE. - person Tyson; 21.07.2015
comment
Хм, сотрите это. Возможно, я был слишком поспешным, назвав это ответом. Это работает с Java 8. Я не был уверен, что было в этой системе, но System.out.println(System.getProperty(java.version)) дает мне 1.8.0_45. Согласно вашему объяснению, я не должен видеть эту проблему в Java 8, верно? - person Tyson; 21.07.2015
comment
Я так не думаю. Я также думаю, что вам нужно было явно включить альтернативную реализацию хэша. Удачи в поиске проблемы - person dkatzel; 21.07.2015
comment
облом. Я определенно не включал реализацию альтернативного хеширования, по крайней мере, не сознательно. Я пошел дальше и пока снял пометку с этого ответа, но я очень ценю, что вы нашли время, чтобы оставить этот подробный ответ! - person Tyson; 21.07.2015
comment
Вы говорите о String#hash32, но здесь важно URL#hashCode. На самом деле, String#hashCode вообще не изменился, просто его избегают в некоторых HashMap. Я скорее уверен, что Ломбок не использует hash32. - person maaartinus; 23.07.2015
comment
Да, в исходном вопросе была опечатка, где было String вместо URL, что привело к путанице. - person dkatzel; 24.07.2015

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

person Raedwald    schedule 21.07.2015
comment
Я понял это, когда читал на эту тему, но один вопрос все еще остается без ответа. URL.hashCode(), как ни странно, возвращает детерминированное значение между экземплярами JVM, так зачем же обертывать это в объект с генерируемым Lombok hashCode(), который вызывает только URL.hashCode(), добавляя недетерминированность? Возможно, ответ заключается в том, что генерируются только 4 разных целых числа, даже если я раскручиваю JVM тысячу раз? Не знаю, мне все равно непонятно... - person Tyson; 21.07.2015