Можно ли гарантировать порядок элементов в HashSet при повторных запусках
Что проверяют
Отличаете ли вы контракт от наблюдаемого поведения. Вопрос с подвохом: на практике порядок часто повторяется, но гарантии нет, и важно объяснить, от чего он зависит. Следом спрашивают, какую коллекцию взять, если порядок нужен.
Как отвечать
Гарантировать нельзя. Javadoc HashSet прямо говорит, что порядок обхода не определён и может меняться со временем. Внутри лежит HashMap, и позиция элемента зависит от нескольких вещей:
hashCode()элемента: уStringиIntegerон считается по фиксированной формуле, поэтому на той же версии JDK и при той же истории вставок порядок от запуска к запуску обычно совпадает;- identity hash: у классов без переопределённого
hashCode(), а также уenumхэш привязан к конкретному объекту в JVM, и порядок меняется между запусками; - ёмкость и история: при расширении таблицы элементы перераспределяются по корзинам, коллизии складываются в список, а при большом их числе в дерево. Другой начальный размер или порядок вставки дают другой порядок обхода;
- версия JDK: реализация
HashMapменялась, заметнее всего в Java 8, и тесты, завязанные наtoString()множества, после обновления падали.
Отдельно стоит упомянуть Set.of(...): у неизменяемых множеств из Java 9 порядок обхода нарочно перемешивается солью, которая выбирается при старте JVM.
Если порядок важен: LinkedHashSet сохраняет порядок вставки и с Java 21 реализует SequencedSet с getFirst() и reversed(), TreeSet держит сортировку по Comparator, EnumSet обходит константы в порядке объявления. Для вывода достаточно отсортировать результат.
Где спотыкаются
Говорят, что порядок в HashSet случайный при каждом запуске. Для строк и чисел это неправда, и интервьюер попросит объяснить, откуда тогда случайность.
Видят, что маленькие Integer выходят отсортированными, и делают вывод, что HashSet сортирует. Это совпадение: хэш числа равен ему самому, и оно попадает в корзину со своим номером.
Сравнивают в тестах set.toString() с готовой строкой. Тест проходит локально и падает на другой JDK или после смены набора данных.