How to keep unique objects by a field
Short answer
distinct() compares whole objects. To compare by one field, remember the values you have already seen in a Set:
record Order(int id, String customer) {}
void main() {
var orders = List.of(new Order(1, "Anna"), new Order(2, "Oleg"), new Order(3, "Anna"));
var seen = new HashSet<String>();
var firstPerCustomer = orders.stream().filter(o -> seen.add(o.customer())).toList();
IO.println(firstPerCustomer);
}Output:
[Order[id=1, customer=Anna], Order[id=2, customer=Oleg]]seen.add() returns false for a repeated value, and that element is filtered out.
With toMap
A way without outside state: collect a Map by the field and take its values:
record Order(int id, String customer) {}
void main() {
var orders = List.of(new Order(1, "Anna"), new Order(2, "Oleg"), new Order(3, "Anna"));
var lastPerCustomer = orders.stream()
.collect(Collectors.toMap(Order::customer, o -> o, (first, second) -> second, LinkedHashMap::new))
.values();
IO.println(lastPerCustomer);
}Output:
[Order[id=3, customer=Anna], Order[id=2, customer=Oleg]]With a TreeSet
A TreeSet with a comparator by the field treats objects with an equal field as equal:
record Order(int id, String customer) {}
void main() {
var orders = List.of(new Order(1, "Oleg"), new Order(2, "Anna"), new Order(3, "Oleg"));
var unique = new TreeSet<>(Comparator.comparing(Order::customer));
unique.addAll(orders);
IO.println(unique);
}Output:
[Order[id=2, customer=Anna], Order[id=1, customer=Oleg]]Common mistake
Using the Set trick in a parallel stream. HashSet is not thread-safe, and the result becomes unpredictable. In a parallelStream() use the toMap() variant.
Functions used in this recipe
- Collectors.toMap() — A collector that builds a map from stream elements.
- Stream.distinct() — Removes duplicate elements from a stream.
- TreeSet — A set that keeps its elements in sorted order.