Explain Codes LogoExplain Codes Logo

Java 8 Distinct by property

java
prompt-engineering
functions
collections
Anton ShumikhinbyAnton Shumikhin·Oct 10, 2024
TLDR

For distinct elements by a property in Java 8, you want to team up Stream's filter with a powerful stateful Predicate based on a HashSet. This dynamic duo ensures uniqueness based on your specified property, which we'll demonstrate with the propertyName of Item objects:

// Superheroes assemble! Set<String> seen = ConcurrentHashMap.newKeySet(); List<Item> distinctItems = items.stream() // if seen.add() returns true, the item is a fresh face. Add it, my friend! .filter(i -> seen.add(i.getPropertyName())) .collect(Collectors.toList());

Here, we use Item::getPropertyName for the .add method because, well, no one likes a party with only Bobs, right?

Solutions for every Java coder

Duplicates? Not on my watch: Collectors.toMap()

If you dislike duplicated properties as much as I dislike pineapple on pizza, Collectors.toMap() is your new BFF. It brilliantly manages duplicates, deciding their fate with a merge function:

List<Item> distinctItems = items.stream() .collect(Collectors.toMap( Item::getPropertyName, Function.identity(), // existing gets the mic if colliding with replacement. It's seniority, folks! (existing, replacement) -> existing )).values().stream().collect(Collectors.toList());

TreeSet: the ultimate order-keeper

Want uniqueness AND order for custom objects? Say hello to TreeSet with a custom comparator:

Set<Item> distinctItems = new TreeSet<>(Comparator.comparing(Item::getPropertyName)); // All are welcome, but no doppelgängers, please! distinctItems.addAll(items);

Wrap it up with Immutability: Wrapper classes

Craft an immutable wrapper class to enforce uniqueness without tweaking your original class's equality considerations:

public class UniquePropertyWrapper<T> { // Your item chilling dormant in the wrapper private T value; // Wrapper stuff omitted for brevity // It's hip to be square...or, I mean, equal. @Override public boolean equals(Object o) { // Place your custom equals logic here } // Diversity encouraged here! @Override public int hashCode() { // Insert custom hashCode logic here } }

The Swiss knife: Stream Filters

Track 'em all: Predicate tracking

Predicate helps you keep tabs on the seen elements, making it super reusable:

public <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) { Set<Object> seen = ConcurrentHashMap.newKeySet(); // If it's not seen before, it's a 'yes' from me return t -> seen.add(keyExtractor.apply(t)); }

Smart and concise: Inline lambda expressions

If sorting is your game, inline lambda expressions are your fame. Use these with TreeSet to define a comparator on-the-spot:

// Red carpets out for our VIP comparator! Set<Item> distinctItems = new TreeSet<>((i1, i2) -> i1.getPropertyName().compareTo(i2.getPropertyName())); // More the merrier, but no clones please! distinctItems.addAll(items);

List surgery: Modify original list

The removeIf() method lets you do a "plastic surgery" on the original list. Pair it with a Predicate for a safe operation:

// It's a purge!: Remove all who don't stand out items.removeIf(distinctByKey(Item::getPropertyName).negate());

All streams lead to Rome: Concurrent handling

Using ConcurrentHashMap.newKeySet() is an ace move for thread safety and handling parallel streams:

Set<String> seen = ConcurrentHashMap.newKeySet(); List<Item> parallelDistinctItems = items.parallelStream() .filter(i -> seen.add(i.getPropertyName())) .collect(Collectors.toList()); // Thread-safe and neat, just the way you like it!