Definition
map(Employee::getDepartment)projects each employee to its department string.distinct()removes duplicates by callingequals()andhashCode(), keeping only the first occurrence.- Stateful operation:
distinct()internally maintains aSetof seen values. - Order: preserves encounter order on sequential streams; on parallel,
unordered()is faster. - Alternative:
collect(Collectors.toSet())gives uniqueness without a separatedistinct()call.
The HR manager at the National Bank of India wants the list of departments that actually exist, each named once, to plan an org chart. Many employees share a department, so she writes down each department the first time she sees it and ignores repeats. map plus distinct is exactly that.
Understanding the Concepts
What does map plus distinct do?
First you project each employee to its department with map(Employee::getDepartment), turning the stream of employees into a stream of department strings. That stream will contain repeats because many employees share a department. The distinct() intermediate operation then removes duplicates, keeping only the first occurrence of each value in encounter order.
How do you extract unique departments?
package com.bank.hr;
import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;
public class UniqueDepartments {
public static void main(String[] args) {
List<Employee> staff = List.of(
new Employee("Rajesh", "Loans", new BigDecimal("95000")),
new Employee("Priya", "IT", new BigDecimal("120000")),
new Employee("Amit", "Finance", new BigDecimal("85000")),
new Employee("Kavita", "Loans", new BigDecimal("78000")),
new Employee("Suresh", "IT", new BigDecimal("110000")));
List<String> departments = staff.stream()
.map(Employee::getDepartment)
.distinct()
.collect(Collectors.toList());
System.out.println(departments);
}
}
[Loans, IT, Finance]
How does distinct() decide uniqueness?
distinct() uses equals() and hashCode() to decide whether two elements are the same. For strings this works out of the box because String has well-defined equals and hashCode. If you were distinct-ing custom objects you would have to override both methods correctly, otherwise distinct would treat every object as unique by identity.
How is distinct() different from toSet()?
| Approach | Result type | Preserves order? | Extra operation? |
|---|---|---|---|
| distinct() + toList() | List
| toSet() | Set
| toCollection(LinkedHashSet::new) | Set
// Using toSet() instead of distinct
import java.util.Set;
Set<String> deptSet = staff.stream()
.map(Employee::getDepartment)
.collect(Collectors.toSet());
System.out.println(deptSet); // unordered
What are common mistakes?
Mistake: forgetting to override equals and hashCode on custom objects used with distinct.
// WRONG: Department class without equals/hashCode
// distinct() compares by identity, so duplicates survive
List<Department> unique = deptList.stream()
.distinct()
.collect(Collectors.toList());
// All objects survive because they are different references!
// CORRECT: override equals and hashCode in Department
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Department d)) return false;
return Objects.equals(name, d.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
What about parallel streams and distinct?
distinct() is stateful because it must remember the values it has already seen. On a sequential stream it preserves encounter order. On a parallel stream maintaining order is more expensive, so if order does not matter you can call unordered() before distinct to let the engine drop the ordering constraint and run faster.
// Parallel with unordered for better performance
List<String> depts = staff.parallelStream()
.map(Employee::getDepartment)
.unordered()
.distinct()
.collect(Collectors.toList());
What about null department values?
If any employee has a null department, distinct() itself does not crash, but the null will appear in the result list. Filter it out if that is not desired.
List<String> safeDepts = staff.stream()
.map(Employee::getDepartment)
.filter(java.util.Objects::nonNull)
.distinct()
.collect(Collectors.toList());
What should a senior engineer know?
distinct()internally uses aLinkedHashSeton sequential streams to maintain insertion order.- On very large datasets with many duplicates, the internal set can consume significant memory.
- If you only need to check membership or count unique values,
toSet()orCollectors.toUnmodifiableSet()is simpler. - The choice between distinct-then-toList and toSet depends on whether you need encounter order.
What are common interview follow-up questions?
- "What does distinct use internally?" An internal Set based on equals and hashCode.
- "Does distinct preserve order?" Yes on sequential streams; on parallel it depends on the unordered flag.
- "When is toSet better than distinct plus toList?" When you do not need ordered output.
Visual Overview
Stream<Employee>
[Rajesh/Loans, Priya/IT, Amit/Finance, Kavita/Loans, Suresh/IT]
│
│ .map(Employee::getDepartment)
▼
Stream<String>
["Loans", "IT", "Finance", "Loans", "IT"]
│
│ .distinct() (internal Set tracks seen values)
│ Loans → first time → keep
│ IT → first time → keep
│ Finance→ first time → keep
│ Loans → already seen → skip
│ IT → already seen → skip
▼
Stream<String>
["Loans", "IT", "Finance"]
│
│ .collect(toList())
▼
List<String> [Loans, IT, Finance]
Key Takeaway
To get unique values of a field I combine two operations: I map each object to the field I care about, then I call distinct to remove the duplicates. For departments I mapped every employee to its department string, which produces a stream full of repeats because many people share a department, and then distinct kept only the first occurrence of each, giving me each department exactly once in the order it first appeared.
In my banking project at the National Bank of India this was building the definitive department list for a new org chart. The employee list had hundreds of people with the same department names repeating, so I projected to the department and applied distinct to collapse the repeats.
A senior point I always add is how distinct decides uniqueness: it relies on equals and hashCode. For strings this just works, but for custom objects I must override both properly. I also mention that distinct is stateful and keeps an internal set, and that collecting straight into a Set is simpler when I do not need encounter order.
Remember one sentence: map(Employee::getDepartment).distinct() projects and deduplicates in one pipeline, relying on equals and hashCode for uniqueness.
---