How to find common elements and the difference of two lists
Short answer
retainAll() keeps only the elements that are also present in the other collection:
void main() {
var a = new ArrayList<>(List.of(1, 2, 3, 4));
var b = List.of(3, 4, 5);
a.retainAll(b);
IO.println(a);
}Output:
[3, 4]The difference
removeAll() removes the elements present in the other collection — what remains is not in the other one:
void main() {
var all = new ArrayList<>(List.of("Anna", "Oleg", "Boris"));
var done = List.of("Oleg");
all.removeAll(done);
IO.println(all);
}Output:
[Anna, Boris]Without changing the lists
A stream with a HashSet creates a new list and leaves the originals alone:
void main() {
var a = List.of(1, 2, 3, 4);
var b = new HashSet<>(List.of(3, 4, 5));
IO.println(a.stream().filter(b::contains).toList());
IO.println(a.stream().filter(n -> !b.contains(n)).toList());
}Output:
[3, 4]
[1, 2]Symmetric difference
Elements that are in only one of the lists:
void main() {
var a = new HashSet<>(List.of(1, 2, 3));
var b = new HashSet<>(List.of(2, 3, 4));
var onlyA = new TreeSet<>(a);
onlyA.removeAll(b);
var onlyB = new TreeSet<>(b);
onlyB.removeAll(a);
onlyA.addAll(onlyB);
IO.println(onlyA);
}Output:
[1, 4]Common mistake
Passing a large List to retainAll() or filter(list::contains). Every contains() on a list scans it fully. Turn the second collection into a HashSet — on large data this makes the code hundreds of times faster.
Functions used in this recipe
- Set.retainAll() — Keeps only the elements that are also in another collection (intersection).
- List.remove() — Removes a list element by index or by value.
- HashSet — A set without duplicates backed by a hash table; order is not guaranteed.
- Stream.filter() — Keeps only the stream elements that match a condition.