Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Collectors.toMap write a merge function on a different attribute of object than the one which is not used as value

I need to create Map<String, String> from List<Person> using Stream API.

persons.stream()
       .collect(Collectors
            .toMap(Person::getNationality, Person::getName, (name1, name2) -> name1)

But in the above case, I want to resolve conflict in name attribute by using Person's age. is there any way to pass merge function something around the lines (age1, age2) -> // if age1 is greater than age2 return name1, else return name2 ?

like image 1000
Govinda Sakhare Avatar asked Jun 12 '19 12:06

Govinda Sakhare


People also ask

What is Merge function in collectors toMap?

Collectors.toMap() with Mapper and Merge FunctionsIt's input are two values that is the two values for which keyMapper returned the same key, and merges those two values into a single one.

What is collectors in Java?

Java Collectors. Collectors is a final class that extends Object class. It provides reduction operations, such as accumulating elements into collections, summarizing elements according to various criteria, etc. Java Collectors class provides various methods to deal with elements.

How do I collect a stream map?

Method 1: Using Collectors.toMap() Function The Collectors. toMap() method takes two parameters as the input: KeyMapper: This function is used for extracting keys of the Map from stream value. ValueMapper: This function used for extracting the values of the map for the given key.


2 Answers

To select a person based on its age, you need the Person instance to query the age. You cannot reconstitute the information after you mapped the Person to a plain name String.

So you have to collect the persons first, to be able to select the oldest, followed by mapping them to their names:

persons.stream()
    .collect(Collectors.groupingBy(Person::getNationality, Collectors.collectingAndThen(
        Collectors.maxBy(Comparator.comparingInt(Person::getAge)),
        o -> o.get().getName())));
like image 50
Holger Avatar answered Sep 19 '22 15:09

Holger


If you don't want to use a helper data structure, it is possible if you first keep your Person info and perform the merge based on it and apply the mapping afterwards:

public void test() {
    final List<Person> persons = new ArrayList<>();

    final BinaryOperator<Person> mergeFunction =
        (lhs, rhs) -> lhs.getAge() > rhs.getAge() ? lhs : rhs;

    final Function<Person, String> mapFunction = Person::getName;

    final Map<String, String> personNamesByNation =
        persons.stream()
            .collect(
                Collectors.groupingBy(Person::getNation, // KeyMapper Person.getNation: Map<String, List<Person>>
                    Collectors.collectingAndThen(
                        Collectors.collectingAndThen(
                            Collectors.reducing(mergeFunction), // Merge Persons into single value via merge function: Map<String, Optional<Person>>
                            Optional::get), // unwrap value: Map<String, Person>
                        mapFunction))); // apply map function afterwards: Map<String, String>
}
like image 32
sfiss Avatar answered Sep 19 '22 15:09

sfiss