Как удалить дубликаты из списка в Java
Удаление дубликатов из списка Java с помощью HashSet, LinkedHashSet или метода distinct потоков.
List в Java допускает дубликаты по умолчанию, поэтому когда каждое значение должно встречаться только один раз, повторы нужно удалять самостоятельно. В этой главе показаны идиоматические способы сделать это с учётом того, сохраняется ли исходный порядок вставки.
Использование LinkedHashSet (порядок сохраняется)
Наиболее простой подход — скопировать список в множество, поскольку Set автоматически отвергает дубликаты. Используйте LinkedHashSet вместо обычного HashSet, чтобы сохранить порядок первого появления элементов:
List<String> unique = new ArrayList<>(new LinkedHashSet<>(list));Обернув множество обратно в ArrayList, вы снова получаете List, готовый для индексирования или дальнейшей работы. LinkedHashSet выполняет всю тяжёлую работу: при заполнении из исходного списка он автоматически отбрасывает уже виденные элементы, а благодаря связанной структуре запоминает порядок их первого появления.
Если порядок не важен, обычный HashSet работает чуть быстрее и использует немного меньше памяти. Однако он перемешивает порядок элементов, что редко бывает желательным при отображении списка, поэтому LinkedHashSet является безопасным выбором по умолчанию.
Использование Stream API
Начиная с Java 8, Stream.distinct() удаляет дубликаты в одном читаемом конвейере. Как и LinkedHashSet, он сохраняет порядок встречи элементов:
List<String> unique = list.stream()
.distinct()
.collect(Collectors.toList());distinct() сравнивает элементы с помощью equals() и hashCode(), точно так же, как это делает множество, поэтому для пользовательских типов необходимо корректно реализовать эти методы. Этот подход особенно удобен, когда дедупликация является одним из шагов в более крупном конвейере — вы можете добавлять filter, map или sorted вокруг него без создания временной коллекции.
Сравнение подходов
Оба распространённых метода основаны на equals/hashCode и оба сохраняют порядок вставки; разница в основном заключается в стиле и контексте.
| Подход | Порядок сохраняется? | Лучше когда |
|---|---|---|
LinkedHashSet | Да | Быстрое однострочное решение без зависимостей |
HashSet | Нет | Порядок не важен, а скорость критична |
stream().distinct() | Да | Дедупликация является частью более крупного конвейера |
Ключевой момент для всех из них: они создают новую коллекцию, не изменяя источник. Если нужно дедуплицировать на месте, можно очистить список и заново добавить уникальные элементы или присвоить результат той же переменной.
Рабочий пример
Что можно вынести из запуска:
- Исходный список сохраняет все 7 элементов, включая повторяющиеся
javaиsql, посколькуListдопускает дубликаты. - Результат
LinkedHashSetсодержит только 4 элемента —[java, sql, api, rest]— и они располагаются в порядке первого появления, а не в отсортированном или случайном порядке. - Результат
stream().distinct()идентичен по размеру и порядку, что подтверждает взаимозаменяемость двух техник в данном случае. deduped.equals(viaStream)выводитtrue, так как два списка равны, когда содержат одинаковые элементы в одинаковом порядке.- Исходный список
tagsостаётся без изменений, следовательно, операции дедупликации создают новые списки, а не изменяют источник.