Encodera

Thanks for reading!

Donate to the site — we work hard for you.

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

Thanks for reading!

Donate to the site — we work hard for you.