W3docs

Как удалить дубликаты из списка в 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()ДаДедупликация является частью более крупного конвейера

Ключевой момент для всех из них: они создают новую коллекцию, не изменяя источник. Если нужно дедуплицировать на месте, можно очистить список и заново добавить уникальные элементы или присвоить результат той же переменной.

Рабочий пример

java— editable, runs on the server

Что можно вынести из запуска:

  • Исходный список сохраняет все 7 элементов, включая повторяющиеся java и sql, поскольку List допускает дубликаты.
  • Результат LinkedHashSet содержит только 4 элемента — [java, sql, api, rest] — и они располагаются в порядке первого появления, а не в отсортированном или случайном порядке.
  • Результат stream().distinct() идентичен по размеру и порядку, что подтверждает взаимозаменяемость двух техник в данном случае.
  • deduped.equals(viaStream) выводит true, так как два списка равны, когда содержат одинаковые элементы в одинаковом порядке.
  • Исходный список tags остаётся без изменений, следовательно, операции дедупликации создают новые списки, а не изменяют источник.

Практика

Практика
Какой тип коллекции удаляет дубликаты, сохраняя исходный порядок вставки элементов?
Какой тип коллекции удаляет дубликаты, сохраняя исходный порядок вставки элементов?
Was this page helpful?