5

I am creating a Map<String,String> from List<Person> using java-8 stream

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

But in above case I want to resolve conflict in name 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 ?

7

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

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>
}
  • 1
    You can also use final BinaryOperator<Person> mergeFunction = BinaryOperator.maxBy(Comparator.comparingInt(Person::getAge));, which is what you get when using Collectors.maxBy(…) instead of Collectors.reducing(…), so these solutions are equivalent. – Holger Jun 12 at 12:59
  • Hm, true, when I started your answer wasn't there yet. I guess my answer also works if you want to aggregate Persons into a new Person object, but for the question of the OP yours looks cleaner. – sfiss Jun 12 at 13:03
  • 1
    Indeed, it was overlapping. Nothing wrong with that. I just wanted to mention the equivalence, in case, any reader is wondering whether there are functional differences. – Holger Jun 12 at 13:04
1

order the elements of stream by age and then just choose first:

persons.stream()
       .sorted(Comparator.comparing(Person::getAge).reversed())
       .collect(Collectors.toMap(Person::getNationality, Person::getName, (n1, n2) -> n1));

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.