Search code examples
javalistjava-streammax

How to conditionally find the maximum based on multiple conditions in Java streams


I have a list of Person objects.

I want to find a person either has employment with the maximum age, else return any person the with the maximum age.

I have written the following code. Is it fine, or is there a better approach?

List <Person> personList;
    
Person person = personList.stream()
    .filter(p -> p.isEmployed())
    .max(Comparator.comparing(Person::getAge))
    .orElse(personList.stream()
                .max(Comparator.comparing(Person::getAge))
                .orElse(null));

Solution

  • I have a list of Person. I want to find a person either have employment with maximum age else return person with maximum age.

    It can be done in a single pass through the list by creating an intermediate map, using collector partioningBy() and providing collector maxBy() as the downstream.

    This combination will create a map containing two entries with keys false and true and values of type Optional<Person>. Because maxBy() produces an optional result.

    Firstly, we have to get rid of empty optional by applying filter().

    Then we can sort these entries in reversed order by key, so that true will come before false (the natural order: false -> true). I.e. employed person having the greatest age will come first.

    The last step is to apply findFirst() and extract the value from the resulting map entry.

    Because we are sorting only two entries, it will not cause any tangible overhead and the code will run linear time (reminder: maxBy() runs in O(n) time, performing a single pass through the data set).

    List<Person> personList =
        List.of(new Person(23, false), new Person(32, true),
                new Person(36, true), new Person(39, false),
                new Person(19, false), new Person(21, true));
    
    Person person = personList.stream()
        .collect(Collectors.partitioningBy(Person::isEmployed, // intermediate map Map<Boolean, Optional<Person>>
            Collectors.maxBy(Comparator.comparing(Person::getAge))))
        .entrySet().stream()
        .filter(entry -> entry.getValue().isPresent())
        .sorted(Map.Entry.<Boolean, Optional<Person>>comparingByKey().reversed())
        .findFirst()              // Optional<Map.Entry<Boolean, Optional<Person>>>
        .map(Map.Entry::getValue) // Optional<Optional<Person>>
        .map(Optional::get)       // Optional<Person>
        .orElse(null);
    
    System.out.println(person);
    

    Output:

    Person {  age = 36, isEmployed = true }
    

    A link to the Online Demo