Home > database >  Java remove entries from list where certain attributes are duplicated
Java remove entries from list where certain attributes are duplicated

Time:12-23

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.

CodePudding user response:

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

  • keyMapper - a function which generates a Key out of a stream element;
  • *valueMapper - a function producing Value from a stream element;
  • mergeFunctino - a function responsible for resolving Values mapped to the same Key;
  • mapFunctory - allows to specify the required type of Map.

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();

CodePudding user response:

You can override the equals and hashCode methods in your ValueObject class (not tested):

public class ValueObject {

    private String firstName;
    private String lastName;
    private String adress;

    // Constructor...

    // Getters and setters...

    @Override
    public boolean equals(Object obj) {
        return obj == this ||(obj instanceof ValueObject
                    && ((ValueObject) obj).firstName.equals(this.firstName)
                    && ((ValueObject) obj).lastName.equals(this.lastName)
                );
    }


    @Override
    public int hashCode() {
        return (firstName   lastName).hashCode();
    }
    
}

Then all you need is to use Stream#distinct to remove the duplicates

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

You can read this answer for a little more information about.

CodePudding user response:

I cannot put it in a stream. But if you indeed create a equals method in the valueObject based on name and first name, you can filter based on the object. Put this in the ValueObject:

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ValueObject that = (ValueObject) o;
        return Objects.equals(firstName, that.firstName) && Objects.equals(lastName, that.lastName);
    }

than it should you work fine with a loop like this:

List<ValueObject> listWithoutDuplicates = new ArrayList<>();

for(var vo: listWithDuplicates){
   if(!listWithoutDuplicates.contains(vo)){
        listWithoutDuplicates.add(vo);
         }
 }

But in a stream would be nicer, .. But you can work that out if you've implemented the equals method.

CodePudding user response:

I like the already provided answers, and think they give answer for you, but maybe for learning purposes, another approach could be to use a Collector that compares firstName and lastName fields of each ValueObject and then retains only the last instance of duplicates.

List<ValueObject> filteredList = listWithDuplicates.getValues().stream()
    .collect(Collectors.collectingAndThen(
        Collectors.toMap(
            vo -> vo.getLastName()   vo.getFirstName(), 
            vo -> vo, 
            (vo1, vo2) -> vo2
        ), 
        map -> new ArrayList<>(map.values())
    ));

So you would group the ValueObject instances by lastName and firstName using a COllector that creates a Map and the keys for the map are the concatenated lastName and firstName. The collectingAndThen collector transforms the Map into a List of ValueObjects.

  • Related