Find Common Elements in Lists in Java
Problem
Finding the common elements between two Lists means keeping only the values that appear in both — Java's retainAll() removes everything from one list that isn't also present in another.
Given two Lists, find the elements that appear in both.
Java Program
import java.util.ArrayList;
import java.util.List;
public class FindCommonElementsInLists {
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<>(List.of(1, 2, 3, 4, 5));
List<Integer> list2 = new ArrayList<>(List.of(3, 4, 5, 6, 7));
List<Integer> common = new ArrayList<>(list1); // copy — list1 itself stays untouched
common.retainAll(list2); // keeps only elements also present in list2
System.out.println(common);
}
}Output
Core Logic
Copying the first list, then calling retainAll() with the second, strips out every element the copy doesn't share with the second list, leaving only the intersection.
- 1
new ArrayList<>(list1)makes a copy oflist1, so the original isn't modified by what comes next. - 2
common.retainAll(list2)removes every element fromcommonthat isn't also present inlist2, keeping only the shared values. - 3
list1andlist2both stay exactly as they were — only the separatecommonlist changes. - 4What's left in
commonis exactly the elements present in both original lists.
list1 = [1, 2, 3, 4, 5] and list2 = [3, 4, 5, 6, 7], retainAll() strips 1 and 2 from the copy, leaving [3, 4, 5].Key Point: Calling retainAll() directly on list1 would mutate it in place — copying first keeps both original lists intact, which is usually what you want unless you specifically mean to filter one of them permanently.
Why: For each of the n elements in the copy, retainAll() checks whether it's present in list2 via contains(), which costs O(m) on a plain List — converting list2 to a HashSet first would make each check O(1) on average, bringing the whole operation down to O(n + m).
Key Concepts
Approach 2: Java 8
import java.util.List;
import java.util.stream.Collectors;
public class FindCommonElementsInListsStream {
public static void main(String[] args) {
List<Integer> list1 = List.of(1, 2, 3, 4, 5);
List<Integer> list2 = List.of(3, 4, 5, 6, 7);
// Keeps only the elements from list1 that list2 also contains
List<Integer> common = list1.stream()
.filter(list2::contains)
.collect(Collectors.toList());
System.out.println(common);
}
}
Output
Core Logic
Filtering list1's stream down to only the elements list2 also contains reproduces the same intersection as a pipeline, without a separate copy-and-retainAll step.
- 1
list1.stream()opens a stream over the first list's elements. - 2
.filter(list2::contains)keeps only the elements that are also present inlist2, using a method reference in place of a lambda. - 3
.collect(Collectors.toList())gathers the surviving elements into a new List. - 4Neither original list is modified —
list1is only read from, andcommonis a completely new List.
list1 = [1, 2, 3, 4, 5] and list2 = [3, 4, 5, 6, 7], filtering keeps only 3, 4, and 5, producing [3, 4, 5].Key Point: list2::contains is a method reference passed directly as the filter predicate — it reads as 'keep elements list2 contains', with no separate retainAll() call or intermediate copy needed.
Why: filter() still calls contains() once per element of list1, each costing O(m) on a plain List — the same asymptotic cost as retainAll(), just expressed as a pipeline instead of a mutating call.