Почему при сравнении по equals не происходит проверка hashCode (в частности? в классе String)

Если два объекта равны с точки зрения equals, то значения, возвращаемые hashCode, также будут одинаковые. Следовательно, если у двух объектов разные хэши, то объекты должны считаться разными с точки зрения equals. Почему же во всех встреченных мною реализациях equals нет проверки на несовпадение по хэшу? Особенно, необъяснимо для меня отсутствие проверки хешей при сравнении неизменяемых объектов, в которых хэш кэшируется в момент создания. Яркий пример String.

Если сравниваются две огромные строки, у которых уже вычислен хэш, и этот хэш разный, то почему бы после проверки на "==" не проверить, что хеши различаются и сразу вернуть false.

Понимаю, что упускаю какой-то важный решающий момент, но поймать его не могу.


Ответы (2 шт):

Автор решения: had0uken

Не совсем понятен вопрос. Оператор "==" не проверяет "одиннаковость" объектов, он проверяет ссылаются ли переменные на один и тот же объект.

Почему же во всех встреченных мною реализациях equals нет проверки на несовпадение по хэшу?

Проверка по equals долгая. Если у объекта переопределены методы equals и hashCode, при сравнении объектов сам метод equals вызовется только в случае, если их hashCode совпали. Если hashCode не совпали, то до метода equals() дело не доходит, возвращается значение false. Считайте процесс двухэтапным. Первый этап (быстрый) сравниваются hash. Если они НЕ равны друг другу - возвращается false. Если равны - запускается второй этап (медленный) - метод equals(). И за ним уже окончательное решение равны ли объекты или нет. Второй этап запускается чтобы предотвратить "коллизию". Коллизия - это момент, когда у объектов hash совпадает, но они не равны. Такое достаточно редко, но бывает. Объясняется это тем, что hashCode() возвращает int число. То есть это конечное значение. А объектов вы можете создать бесконечное колличество. То есть ситуация при которой два разных объекта будут иметь одинаковый hash - реальна. Вот для таких целей и существует метод equals. Больше он, фактически, не вызывается. Так, как он, как я указал выше, куда медленней чем hash.

→ Ссылка
Автор решения: insolor

При проверках на .equals() не в коллекциях HashSet или HashMap у вас получится, что вы при каждой проверке заново рассчитываете хэш обоих сравниваемых значений, само по себе это не ускорит сравнение, а наоборот добавит лишний этап.

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

Т.е. если сильно нужно, то можно добавить в классе отдельное поле хэша, и по нему внутри equals сравнивать. Но сама функция hashCode обычно пересчитывает хэш заново, поэтому во всех реализациях классов ее вызов в equals скорее ухудшит производительность, а не улучшит.

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

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


По поводу строк. На коротких строках время выполнения методов equals и hashCode будет сопоставимо, получается добавление hashCode (условно) удвоит время сравнения. Это может ухудшить производительность при сравнении большого количества мелких строк. Поэтому проверку hashCode и нет смысла добавлять.

А на длинных строках вы можете самостоятельно добавить проверку хэша, никто не мешает вам это сделать.

→ Ссылка