JavaRush /Курсы /JAVA 25 SELF /Операции union, intersection, difference

Операции union, intersection, difference

JAVA 25 SELF
32 уровень , 2 лекция
Открыта

1. Операция union (объединение множеств)

В Java для представления множеств используется интерфейс Set<T>. В отличие от списков (List), множества гарантируют уникальность элементов и, как правило, не заботятся о порядке (если только не использовать специальные реализации, например, LinkedHashSet). Самые популярные реализации — HashSet и TreeSet. Их основная задача — быстро определять наличие элемента и обеспечивать отсутствие дубликатов.

Когда нужны множества?

  • Когда важна уникальность: например, список всех уникальных пользователей, посетивших сайт.
  • Когда нужно быстро проверять наличие элемента: метод contains у HashSet обычно работает за константное время.
  • Когда требуется выполнять типовые операции над наборами: объединение, пересечение, разность.

Union — это объединение двух и более множеств: результат содержит все элементы из обоих исходных множеств (без повторов).

Пример на практике

Допустим, у нас есть два множества студентов, которые посещают кружки «Робототехника» и «Программирование»:

Set<String> robotics = Set.of("Аня", "Борис", "Вика");
Set<String> programming = Set.of("Вика", "Глеб", "Даша");

Нам нужно получить множество всех студентов, которые ходят хотя бы на один кружок.

Решение через Stream API

Самый прямой способ — объединить оба потока и собрать их в Set:

Set<String> all = Stream.concat(
        robotics.stream(),
        programming.stream()
    ).collect(Collectors.toSet());

System.out.println(all); // [Аня, Борис, Вика, Глеб, Даша]

Пояснение:

  • Stream.concat объединяет два потока.
  • collect(Collectors.toSet()) собирает элементы в множество (автоматически убирая дубликаты).

Альтернатива: более двух множеств

Если у нас три и более кружка, используем Stream.of и flatMap:

Set<String> math = Set.of("Женя", "Вика", "Борис");

Set<String> all = Stream.of(robotics, programming, math)
    .flatMap(Set::stream)
    .collect(Collectors.toSet());

System.out.println(all); // [Аня, Борис, Вика, Глеб, Даша, Женя]

Почему именно Set?
Потому что Set автоматически убирает дубликаты. Если собрать в List, то одни и те же имена встретятся несколько раз.

2. Операция intersection (пересечение множеств)

Intersection — это элементы, которые есть одновременно в обоих множествах.

Пример на практике

Найти студентов, которые ходят и в «Робототехнику», и в «Программирование»:

Set<String> robotics = Set.of("Аня", "Борис", "Вика");
Set<String> programming = Set.of("Вика", "Глеб", "Даша");

Решение через Stream API:

Set<String> both = robotics.stream()
    .filter(programming::contains)
    .collect(Collectors.toSet());

System.out.println(both); // [Вика]

Пояснение:
Мы проходим по всем участникам «Робототехники» и фильтруем только тех, кто есть в «Программировании». Итог — множество с именами, которые есть в обоих кружках.

Альтернативный способ (без Stream API)

Можно использовать встроенный метод retainAll (он изменяет текущее множество):

Set<String> intersection = new HashSet<>(robotics);
intersection.retainAll(programming);
System.out.println(intersection); // [Вика]

Но в рамках темы Stream API акцентируем внимание на стримах.

3. Операция difference (разность множеств)

Difference — это элементы первого множества, которых нет во втором.

Пример на практике

Найти студентов, которые ходят только в «Робототехнику», но не в «Программирование»:

Set<String> robotics = Set.of("Аня", "Борис", "Вика");
Set<String> programming = Set.of("Вика", "Глеб", "Даша");

Решение через Stream API:

Set<String> onlyRobotics = robotics.stream()
    .filter(name -> !programming.contains(name))
    .collect(Collectors.toSet());

System.out.println(onlyRobotics); // [Аня, Борис]

Пояснение:
Мы фильтруем участников «Робототехники», оставляя только тех, кого нет в «Программировании».

Альтернативный способ (без Stream API)

Set<String> difference = new HashSet<>(robotics);
difference.removeAll(programming);
System.out.println(difference); // [Аня, Борис]

4. Практические задачи: обработка списков пользователей

Задача 1: Найти студентов, которые ходят только в один кружок

Нужно узнать, кто ходит только в «Робототехнику» или только в «Программирование», но не в оба сразу. Это симметрическая разность (xor для множеств):

Set<String> onlyOne = Stream.concat(
        robotics.stream().filter(name -> !programming.contains(name)),
        programming.stream().filter(name -> !robotics.contains(name))
    ).collect(Collectors.toSet());

System.out.println(onlyOne); // [Аня, Борис, Глеб, Даша]

Задача 2: Список всех уникальных студентов из нескольких кружков

Set<String> all = Stream.of(robotics, programming, math)
    .flatMap(Set::stream)
    .collect(Collectors.toSet());

System.out.println(all); // [Аня, Борис, Вика, Глеб, Даша, Женя]

Задача 3: Найти студентов, которые не ходят ни в один кружок

Допустим, у нас есть список всех учеников класса:

Set<String> allStudents = Set.of("Аня", "Борис", "Вика", "Глеб", "Даша", "Женя", "Игорь", "Катя");

Нужно узнать, кто не ходит ни в один кружок:

Set<String> attendees = Stream.of(robotics, programming, math)
    .flatMap(Set::stream)
    .collect(Collectors.toSet());

Set<String> notInAny = allStudents.stream()
    .filter(name -> !attendees.contains(name))
    .collect(Collectors.toSet());

System.out.println(notInAny); // [Игорь, Катя]

5. Важные замечания: equals, hashCode и эффективность

Почему важно правильно реализовывать equals и hashCode?

Все операции с множествами (Set) зависят от корректности методов equals и hashCode. Если вы храните объекты собственного класса (например, Student), то обязательно переопределяйте эти методы, иначе сравнения будут работать некорректно.

Пример:

class Student {
    String name;
    int age;

    // Не забудьте переопределить equals и hashCode!
}

Если этого не сделать, два студента с одинаковыми именами и возрастом будут считаться разными объектами для Set.

Почему лучше использовать Set, а не List?

  • Операция contains у Set работает быстро (обычно за константное время).
  • В List поиск элемента происходит за линейное время, что может быть критично для больших коллекций.
  • Для операций над множествами (union, intersection, difference) Set гораздо эффективнее и логичнее.

6. Типичные ошибки при работе с операциями над множествами

Ошибка №1: Использование List вместо Set для операций над множествами. Если вы собираете элементы в List, то дубликаты не удаляются, а операция contains работает медленно. Для union/intersection/difference используйте Set.

Ошибка №2: Не реализован equals/hashCode для объектов. Если вы храните в Set объекты собственного класса, но не переопределили методы equals и hashCode, то пересечение и разность будут работать «странно» — объекты, которые по смыслу одинаковы, не будут считаться равными.

Ошибка №3: Модификация коллекции во время стрима. Если вы прямо в стриме пытаетесь изменять исходный Set (например, добавлять или удалять элементы), получите ConcurrentModificationException. Всегда работайте с новым множеством.

Ошибка №4: Неочевидная потеря порядка. HashSet не гарантирует порядок элементов. Если порядок важен — используйте LinkedHashSet или TreeSet.

Ошибка №5: Использование Stream.concat для более двух коллекций. Stream.concat объединяет только два потока. Для большего количества используйте Stream.of(...) и flatMap.

Ошибка №6: Ошибки с null. Множества не любят null-значения, особенно если вы используете Set.of(...) — он не допускает null. Для работы с null используйте другие реализации или фильтруйте значения заранее.

1
Задача
JAVA 25 SELF, 32 уровень, 2 лекция
Недоступна
Выявление уникальных подозреваемых по разным источникам 🕵️‍♂️
Выявление уникальных подозреваемых по разным источникам 🕵️‍♂️
1
Задача
JAVA 25 SELF, 32 уровень, 2 лекция
Недоступна
Каталогизация артефактов с разных археологических раскопок 🏺
Каталогизация артефактов с разных археологических раскопок 🏺
Комментарии (8)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Evgeniy Fedorov Уровень 3
14 апреля 2026
Вот решение второй задачи Set<Integer> allUniqueArtifacts = Stream.of(siteOneArtifacts, siteTwoArtifacts, siteThreeArtifacts).flatMap(Collection::stream).collect(Collectors.toCollection(TreeSet:: new)); Map<Integer, List<Integer>> result = Stream.of(siteOneArtifacts, siteTwoArtifacts, siteThreeArtifacts).flatMap(Collection::stream).collect(Collectors.groupingBy(n -> n)); Set<Integer> exclusiveSiteFinds = result.entrySet().stream().filter(entry -> entry.getValue().size() == 1).map(Map.Entry::getKey).collect(Collectors.toSet());
Yury Lyutov Уровень 36
19 мая 2026
Сделал иначе, обьеденил все в один поток, потом сделал фильтр вот такой, ну и в конце в Set

 .filter(x ->
            (siteOneArtifacts.contains(x) ? 1 : 0) +
                    (siteTwoArtifacts.contains(x) ? 1 : 0) +
                    (siteThreeArtifacts.contains(x) ? 1 : 0)
                     == 1
1 апреля 2026
Прежде чем сдаться и посмотреть ответ, почитайте оф. документацию на Collectors, на .map, .filter
Riga Уровень 37
4 мая 2026
знать бы ещё как эту документацию читать)
Vany_int64 Уровень 57
29 марта 2026
Боже, как же я кайфую, когда решаю такие задачи, как в этой лекции, это просто чудесно
Big198801 Уровень 34
16 февраля 2026
Вторая задача классная, но сам ее решить не смог - подсмотрел решение(
behindthep Уровень 66
3 марта 2026
Спасибо за честность
Grrbrr7 Уровень 44
27 марта 2026
Бро, не парься. У меня вообще вайбкодинг с дипсиком уже давно, как "душнота" в лекциях появилась