CodeGym /Courses /JAVA 25 SELF /Union, intersection, and difference operations

Union, intersection, and difference operations

JAVA 25 SELF
Level 32, Lesson 2
Available

1. Union operation (set union)

In Java, sets are represented by the Set<T> interface. Unlike lists (List), sets guarantee element uniqueness and generally do not care about order (unless you use special implementations such as LinkedHashSet). The most popular implementations are HashSet and TreeSet. Their main job is to quickly determine whether an element is present and ensure there are no duplicates.

When do you need sets?

  • When uniqueness matters: for example, a list of all unique users who visited a website.
  • When you need fast membership checks: the contains method on HashSet typically runs in constant time.
  • When you need to perform common set operations: union, intersection, difference.

Union is the combination of two or more sets: the result contains all elements from both source sets (without duplicates).

Practical example

Suppose we have two sets of students who attend the clubs “Robotics” and “Programming”:

Set<String> robotics = Set.of("Alice", "Bob", "Victoria");
Set<String> programming = Set.of("Victoria", "George", "Diana");

We need to get the set of all students who attend at least one club.

Solution via the Stream API

The most straightforward way is to concatenate both streams and collect them into a Set:

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

System.out.println(all); // [Alice, Bob, Victoria, George, Diana]

Explanation:

  • Stream.concat concatenates two streams.
  • collect(Collectors.toSet()) collects elements into a set (automatically removing duplicates).

Alternative: more than two sets

If you have three or more clubs, use Stream.of and flatMap:

Set<String> math = Set.of("Eugene", "Victoria", "Bob");

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

System.out.println(all); // [Alice, Bob, Victoria, George, Diana, Eugene]

Why Set?
Because Set automatically removes duplicates. If you collect into a List, the same names will appear multiple times.

2. Intersection operation (set intersection)

Intersection is the set of elements that are present in both sets at the same time.

Practical example

Find students who attend both “Robotics” and “Programming”:

Set<String> robotics = Set.of("Alice", "Bob", "Victoria");
Set<String> programming = Set.of("Victoria", "George", "Diana");

Solution via the Stream API:

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

System.out.println(both); // [Victoria]

Explanation:
We iterate over all “Robotics” participants and keep only those who are also in “Programming”. The result is a set of names that occur in both clubs.

Alternative (without the Stream API)

You can use the built-in retainAll method (it mutates the current set):

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

But within the Stream API topic, we focus on streams.

3. Difference operation (set difference)

Difference is the set of elements in the first set that are not in the second set.

Practical example

Find students who attend only “Robotics” but not “Programming”:

Set<String> robotics = Set.of("Alice", "Bob", "Victoria");
Set<String> programming = Set.of("Victoria", "George", "Diana");

Solution via the Stream API:

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

System.out.println(onlyRobotics); // [Alice, Bob]

Explanation:
We filter the “Robotics” participants, keeping only those who are not in “Programming”.

Alternative (without the Stream API)

Set<String> difference = new HashSet<>(robotics);
difference.removeAll(programming);
System.out.println(difference); // [Alice, Bob]

4. Practical tasks: processing student lists

Task 1: Find students who attend only one club

We need to find those who attend only “Robotics” or only “Programming”, but not both. This is a symmetric difference (xor for sets):

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); // [Alice, Bob, George, Diana]

Task 2: List all unique students from several clubs

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

System.out.println(all); // [Alice, Bob, Victoria, George, Diana, Eugene]

Task 3: Find students who do not attend any club

Suppose we have a list of all students in the class:

Set<String> allStudents = Set.of("Alice", "Bob", "Victoria", "George", "Diana", "Eugene", "Ian", "Kate");

We need to find those who do not attend any club:

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); // [Ian, Kate]

5. Important notes: equals, hashCode, and performance

Why is it important to implement equals and hashCode correctly?

All operations with sets (Set) depend on correct implementations of equals and hashCode. If you store objects of your own class (for example, Student), you must override these methods; otherwise, comparisons will behave incorrectly.

Example:

class Student {
    String name;
    int age;

    // Don't forget to override equals and hashCode!
}

If you don’t, two students with the same name and age will be considered different objects by Set.

Why use Set rather than List?

  • The contains operation on a Set is fast (usually constant time).
  • In a List, searching for an element is linear, which can be critical for large collections.
  • For set operations (union, intersection, difference), Set is much more efficient and semantically appropriate.

6. Common mistakes when working with set operations

Mistake #1: Using List instead of Set for set operations. If you collect elements into a List, duplicates are not removed, and contains is slow. For union/intersection/difference, use a Set.

Mistake #2: equals/hashCode not implemented for objects. If you store objects of your own class in a Set but don’t override equals and hashCode, intersection and difference will behave “strangely” — objects that are logically equal will not be considered equal.

Mistake #3: Modifying a collection during a stream. If you try to modify the source Set directly inside a stream (for example, adding or removing elements), you’ll get a ConcurrentModificationException. Always work with a new set.

Mistake #4: Unintended loss of order. HashSet does not guarantee element order. If order matters, use LinkedHashSet or TreeSet.

Mistake #5: Using Stream.concat for more than two collections. Stream.concat concatenates only two streams. For more, use Stream.of(...) and flatMap.

Mistake #6: Issues with null. Sets don’t play well with null values, especially if you use Set.of(...) — it does not allow null. To handle null, use other implementations or filter values in advance.

1
Task
JAVA 25 SELF, level 32, lesson 2
Locked
Identifying unique suspects from different sources 🕵️‍♂️
Identifying unique suspects from different sources 🕵️‍♂️
1
Task
JAVA 25 SELF, level 32, lesson 2
Locked
Cataloging artifacts from multiple archaeological digs 🏺
Cataloging artifacts from multiple archaeological digs 🏺
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION