Может ли hashCode возвращать отрицательное число
Что проверяют
Понимаете ли вы контракт hashCode() и то, как хеш превращается в индекс бакета. Сам ответ «да» очевиден, интересно, что вы скажете дальше: следом почти всегда просят найти баг в коде с Math.abs(hash) % n.
Как отвечать
Да. Метод возвращает int, и допустимо любое значение, включая отрицательные и Integer.MIN_VALUE. Контракт требует другого: равные по equals() объекты дают одинаковый хеш, а в пределах одного запуска хеш объекта без изменений в полях стабилен. Про знак в контракте ничего нет.
Откуда берутся отрицательные значения:
- переполнение:
String.hashCode()считаетs[0]*31^(n-1) + ... + s[n-1], и на длинных строкахintпереполняется.Objects.hash()устроен так же; - обёртки:
Integer.hashCode()возвращает само число, так что у-5хеш-5; - ваша реализация: любая арифметика над полями.
Как с этим живёт HashMap: хеш сначала перемешивается как h ^ (h >>> 16), затем индекс берётся как (n - 1) & hash, где длина таблицы всегда степень двойки. Маска оставляет только младшие биты, поэтому индекс всегда в диапазоне, и знак хеша на работу не влияет.
Проблема возникает в самописном коде. Math.abs(h) % n ломается на Integer.MIN_VALUE: модуль от него остаётся отрицательным, потому что числа 2^31 в int нет. Итог: отрицательный индекс и ArrayIndexOutOfBoundsException примерно раз на четыре миллиарда значений. Правильно Math.floorMod(h, n) или (h & 0x7fffffff) % n. Строка с хешем ровно Integer.MIN_VALUE существует, например "polygenelubricants".
Где спотыкаются
Пишут Math.abs(key.hashCode()) % shards для шардирования и уверены, что отрицательных индексов не бывает. Баг проявляется редко, поэтому живёт годами.
Используют hashCode() как уникальный идентификатор. Коллизии нормальны: у "Aa" и "BB" хеш одинаковый.
Сохраняют хеш в базу или передают между процессами. Object.hashCode() и хеш enum меняются от запуска к запуску, и партиционирование по ним разъезжается между узлами.