Krzysztof Tkacz
Krzysztof Tkacz

Reputation: 488

Java 8 use streams to distinct objects with duplicated field value

I have a list of objects. Each object has three fields: id, secNumber and type. Type is enum which can have values 'new' or 'legacy'. Sometimes it happens that there are objects in that list which have the same secNumber a but different type.

in such a situation, I need to remove the one with type 'legacy'. How to do it using Java 8 streams?

Upvotes: 0

Views: 2474

Answers (2)

Ousmane D.
Ousmane D.

Reputation: 56393

use toMap with something like this:

Collection<T> result = list.stream()
    .collect(toMap(T::getSecNumber, 
            Function.identity(), 
             (l, r) -> l.getType() == Type.LEGACY ? r : l))
    .values();

where T is the class that contains secNumber, id etc.

  • The keyMapper (T::getSecNumber) extracts each secNumber from each object.
  • The valueMapper (Function.identity()) extracts the objects we want as the map values i.e. the objects from the source them selves.
  • The mergeFunction (l, r) -> is where we say " if two given objects have the same key i.e. getSecNumber then keep the one where their type is 'NEW' and discard the one with 'LEGACY'" and finally we call values() to accumulate the map values into a Collection.

Edit:

following @Tomer Aberbach's comment you may be looking for:

List<T> result = 
            list.stream()
                .collect(groupingBy(T::getSecNumber))
                .values()
                .stream()
                .flatMap(l -> l.stream().anyMatch(e -> e.getType() == Type.NEW) ?
                    l.stream().filter(e -> e.getType() != Type.LEGACY) :
                    l.stream())
                .collect(toList());

The first solution using toMap assumes there can't be multiple objects with the same secNumber and type.

Upvotes: 3

Tomer Aberbach
Tomer Aberbach

Reputation: 646

Assume objects is a List<ClassName> which has been declared and initialized:

List<ClassName> filteredObjects = objects.stream()
    .collect(Collectors.groupingBy(ClassName::getSecNumber))
    .values().stream()
    .flatMap(os -> os.stream().anyMatch(o -> o.getType() == Type.NEW) ?
        os.stream().filter(o -> o.getType() != Type.LEGACY) :
        os.stream()
    ).collect(Collectors.toList());

I made the assumption that objects of type Type.LEGACY should only be filtered out if there exists another object of type Type.NEW which has the same secNumber. I also made the assumption that you could have multiple objects of the same type and secNumber and that those may need to be retained.

Note that the collect(Collectors.groupingBy(ClassName::getSecNumber)) returns a map from whatever type secNumber is to List<ClassName> so calling values() on it returns a Collection<List<ClassName>> which represents a collection of the groupings of objects with the same secNumber.

The flatMap part takes each grouping by secNumber, checks if the grouping has at least one object of Type.NEW, and if so, filters out the objects of type Type.LEGACY, otherwise it just passes along the objects to be flattened into the final List<ClassName>. This is primarily so that if a grouping only has objects of type Type.LEGACY then they are not left out of the final collection.

Upvotes: 3

Related Questions