javaduplicatesjava-streamcollectorsstreams-api

Java remove entries from list where certain attributes are duplicated


I'm new into programming and now facing a tricky problem: I got an Object which contains a list, so the Object looks like the following:

public class SampleClass {
    private UUID id;
    private List<ValueObject> values;
    
    // getters, constructor, etc
}

The ValueObject contains some Strings like:

public class ValueObject {
    private String firstName;
    private String lastName;
    private String address;

    // getters, constructor, etc
}

I have a SampleClass intance which contains a list of multiple ValueObjects. Some of the ValueObjects have the same firstName and lastName.

What I want to archive is that I want to filter out all ValueObject within a SampleClass object having the same firstName and lastName. And I want to keep the last (according to the encounter order) ValueObject out of each group duplicates in the list.

I've tried the following:

SampleClass listWithDuplicates = // intializing SampleClass instance

listWithDuplicates.getValues().stream()
    .collect(Collectors.groupingBy(
        ValueObject::getLastname,
        Collectors.toList()
    ));

To group it by lastname but how do I find then the matching firstNames, because lastname can be equal but firstname can be different so I want to still keep it in my Object as it not equal. And then how to remove the duplicates? Thank you for your help

Update: The order of the list should not get affected by removing the duplicates. And the

listWithDuplicates

holds a SampleClass Object.


Solution

  • You can solve this problem by using four-args version of the Collector toMap(), which expects the following arguments:

    In case if you can't change the implementation of the equals/hashCode in the ValueObject you can introduce an auxiliary type that would serve as a Key.

    public record FirstNameLastName(String firstName, String lastName) {
        public FirstNameLastName(ValueObject value) {
            this(value.getFirstName(), value.getLastName);
        }
    }
    

    Note: if you're OK with overriding the equals/hashCode contract of the ValueObject on it's firstName and lastName then you don't the auxiliary type shown above. In the code below you can use Function.identity() as both keyMapper and valueMapper of toMap().

    And the stream can be implemented like this:

    SampleClass listWithDuplicates = // initializing your domain object
        
    List<ValueObject> uniqueValues = listWithDuplicates.getValues().stream()
        .collect(Collectors.toMap(
            FirstNameLastName::new, // keyMapper - creating Keys
            Function.identity(),    // valueMapper - generating Values
            (left, right) -> right  // mergeFunction - resolving duplicates
            LinkedHashMap::new      // mapFuctory - LinkedHashMap is needed to preserve the encounter order of the elements
        ))
        .values().stream()
        .toList();