Search code examples
javaoption-type

How to chain Optional ifPresent calls?


Suppose you have an Optional and you want to consume the Optional multiple times. You could now save the Optional to a variable; and then use ifPresent two times on it:

Optional<Animal> optionalAnimal = animalService.getAllAnimals().findFirst();
optionalAnimal.ifPresent(Animal::eat);
optionalAnimal.ifPresent(Animal::drink);

Another solution would be to ditch method references and use a lambda that does both:

animalService.getAllAnimals().findFirst()
    .ifPresent(animal -> {
        animal.drink();
        animal.eat();
    });

If I have control over the class that is used in the Optional, I could simply change the methods to use a factory like pattern. So that animal.drink() would return itself. Then I could write:

animalService.getAllAnimals().findFirst()
    .map(Animal::drink)
    .ifPresent(Animal::eat);

But this would be semantically weird. And I don’t always have control over every class that I use in Optionals. And some classes are final, so I could not even extend them to have a factory styled method.

Furthermore, the Optional class is also final, so extending Optional itself is no option either. All of this makes very little sense to me. ifPresent() returns void. If ifPresent() returned the Optional itself (similar to peek() for streams) it would be closer to my goal.

Is there another solution that I did not think of?


What I would like to have is something like this:

animalService.getAllAnimals().findFirst()
    .ifPresent(Animal::drink)
    .ifPresent(Animal::eat);

Solution

  • Consumer.andThen()

    As alternative, you can introduce a method that allows to combine multiple Consumers using Consumer.andThen() and then perform all required side-effects by passing an aggregated Consumer into Optional.ifPresent().

    That's how such method might look like (credits to @Lino for the idea with the first Consumer as an identity):

    @SafeVarargs
    public static <T> Consumer<T> combine(Consumer<T> first, Consumer<T>... others) {
        
        return Arrays.stream(others).reduce(first, Consumer::andThen);
    }
    

    Usage example:

    animalService.getAllAnimals()
        .ifPresent(combine(Animal::drink, Animal::eat));