Можно ли использовать массив в качестве ключа в HashMap?
Что проверяют
Знаете ли вы контракт equals/hashCode и то, что массивы в Java его не переопределяют. Следом обычно просят показать правильную обёртку или спрашивают, что будет, если изменить ключ после put.
Как отвечать
Технически можно: массив это объект, код компилируется и работает. Но работает по ссылке, а не по содержимому:
- почему: массивы наследуют
equalsиhashCodeотObject, то есть сравнение идёт по идентичности. Послеmap.put(new int[]{1, 2}, "x")вызовmap.get(new int[]{1, 2})вернётnull: это другой объект с другим хэш-кодом; - когда это нормально: если вы сознательно храните значения по конкретному экземпляру и достаёте по той же ссылке. Для такого есть и более явный
IdentityHashMap; - ключ по содержимому, вариант 1:
List<Integer>черезList.of(...). Неизменяемый,equalsиhashCodeсчитаются по элементам. Минус: боксинг, на больших массивах примитивов заметно; - вариант 2: своя неизменяемая обёртка, которая копирует массив в конструкторе и считает
Arrays.hashCodeиArrays.equals. Для многомерных массивов нужныArrays.deepHashCodeиArrays.deepEquals; - вариант 3:
TreeMapс компараторомArrays::compare(есть с Java 9), тогдаhashCodeне участвует вовсе; - частные случаи: для
byte[]иногда берутByteBuffer.wrap(bytes), дляchar[]строку.
Обязательно добавьте про изменяемость. Если содержимое ключа поменялось после put (например, обёртка не скопировала массив, и его изменили снаружи), хэш-код станет другим, запись окажется не в той корзине и перестанет находиться через get, хотя память по-прежнему занимает.
Где спотыкаются
Оборачивают массив в record и считают задачу решённой. Сгенерированный equals у записи сравнивает компоненты-массивы по ссылке, так что equals и hashCode всё равно придётся переопределить вручную.
Пишут Arrays.asList(intArray) и получают List<int[]> из одного элемента, а не список чисел. Для примитивов нужен Arrays.stream(a).boxed().toList().
Говорят «массив нельзя использовать как ключ». Можно, и сильный ответ как раз объясняет, что при этом произойдёт.