Как удалить дубликаты из списка
Короткий ответ
distinct() в потоке оставляет первое вхождение каждого значения и сохраняет порядок:
void main() {
var numbers = List.of(3, 1, 3, 2, 1);
IO.println(numbers.stream().distinct().toList());
}Результат:
[3, 1, 2]Через множество
Множество хранит только уникальные значения. LinkedHashSet при этом помнит порядок добавления:
void main() {
var tags = List.of("java", "sql", "java", "git");
var unique = new ArrayList<>(new LinkedHashSet<>(tags));
IO.println(unique);
}Результат:
[java, sql, git]Уникальность по полю
Чтобы оставить по одному объекту для каждого значения поля, соберите их в Map по этому полю:
record User(String email, String name) {}
void main() {
var users = List.of(
new User("anna@site.com", "Anna"),
new User("oleg@site.com", "Oleg"),
new User("anna@site.com", "Anna Smith"));
var unique = users.stream()
.collect(Collectors.toMap(User::email, u -> u, (first, second) -> first, LinkedHashMap::new))
.values();
IO.println(unique);
}Результат:
[User[email=anna@site.com, name=Anna], User[email=oleg@site.com, name=Oleg]]Без учёта регистра
TreeSet с компаратором без учёта регистра считает Java и java одинаковыми:
void main() {
var set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
set.addAll(List.of("Java", "java", "SQL", "sql", "Git"));
IO.println(set);
}Результат:
[Git, Java, SQL]Частая ошибка
Использовать HashSet, если важен порядок. Он не сохраняет порядок добавления:
void main() {
var words = List.of("pear", "apple", "kiwi", "pear", "banana");
IO.println(new HashSet<>(words));
}Результат:
[banana, apple, pear, kiwi]Порядок HashSet зависит от хеш-кодов и может отличаться от исходного. Если порядок важен, берите LinkedHashSet или distinct().
Функции из этого ответа
- Stream.distinct() — Убирает повторяющиеся элементы потока.
- LinkedHashSet — Множество, которое помнит порядок добавления элементов.
- HashSet — Множество без повторов на основе хеш-таблицы; порядок не гарантирован.
- Collectors.toMap() — Сборщик, который строит словарь из элементов потока.