Какие свойства нужно переопределить в классе ключа для корректной работы с HashMap и какие правила должны соблюдаться при переопределении equals и hashCode
Что проверяют
Знаете ли вы контракт equals и hashCode и понимаете ли, где на Android он работает за вас, а где молча ломается. Следом обычно спрашивают про Kotlin: «Что сгенерирует data class и каких свойств это не коснётся?»
Как отвечать
Переопределяются два метода, equals(Object) и hashCode(), только вместе. HashMap по хэшу выбирает корзину, затем внутри неё сравнивает ключи через equals. Правила:
- равенство даёт равный хэш: если
a.equals(b), тоa.hashCode() == b.hashCode(). Обратное не требуется, коллизии допустимы; - свойства
equals: рефлексивность, симметричность, транзитивность,falseпри сравнении сnull; - стабильность: пока не меняются поля, участвующие в сравнении, оба метода возвращают то же самое;
- один набор полей: в
equalsиhashCodeучаствуют одни и те же поля.
На Kotlin ключ обычно объявляют как data class с val-свойствами: equals, hashCode, toString и copy генерируются автоматически. Учитываются только свойства из первичного конструктора, объявленные в теле класса в сравнение не попадают. В Kotlin == вызывает equals, а === сравнивает ссылки.
Покажите, что на Android равенство важно не только для HashMap. От него зависит, пропустит ли StateFlow новое значение: равное текущему по equals он не эмитит. На нём обычно строят DiffUtil.ItemCallback.areContentsTheSame, по нему Compose пропускает рекомпозицию для стабильных параметров. Для ключей типа int вместо HashMap<Integer, V> есть SparseArray, он обходится без упаковки.
Где спотыкаются
Делают ключ data class с var и меняют поле после put. Хэш меняется, запись остаётся в старой корзине, и get возвращает null.
Кладут в data class массив. Массивы сравниваются по ссылке, и два «одинаковых» ключа оказываются разными. Нужен List или ручная реализация через contentEquals и contentHashCode.
Переопределяют только equals, забывая hashCode. Равные ключи попадают в разные корзины, и в карте появляются дубли. В Java есть ещё ловушка: equals(Key other) вместо equals(Object) это перегрузка, и коллекции её не вызывают.