Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Optional not provide a peek method?

I'm curious to know why Java's Optional does not provide a peek method similar to Stream's one.

The peek method javadoc of the Stream interface states:

  • @apiNote This method exists mainly to support debugging, where you want to see the elements as they flow past a certain point in a pipeline

This almost exactly describes my use case:

@Override
@Transactional
public User getUserById(long id) {
    return repository.findById(id)
        .peek(u -> logger.debug("Found user = {} by id = {}", u, id))
        .orElseThrow(() -> new UserNotFoundException("id = " + id));
}

(repository.findById returns Optional<User> (see CrudRepository#findById))

But it won't compile since there is no peek method on Optional.

So without the peek method everything above transforms to:

@Override
@Transactional
public User getUserById(long id) {
  Optional<User> userOptional = repository.findById(id);
  if (userOptional.isPresent()) {
    logger.debug("Found user = {} with id = {}", userOptional.get(), id);
  }
  return userOptional.orElseThrow(() -> new UserNotFoundException("id = " + id));
}

It is also possible to do something like this (see this answer):

@NoArgsConstructor(access = PRIVATE)
public abstract class OptionalUtils {
    public static <T> UnaryOperator<T> peek(Consumer<T> consumer) {
        return t -> {
            consumer.accept(t);
            return t;
        };
    }
}

And use it with the map method:

return repository.findById(id)
    .map(OptionalUtils.peek(u -> logger.debug("Found user = {} with id = {}", u, id)))
    .orElseThrow(() -> new UserNotFoundException("id = " + id));

But I think this is a hack rather than clean usage of Optional.

Since Java 9 it is possible to transform Optional to Stream but the stream does not have the orElseThrow method (and obviously it should not).

Also it is possible to do the same using ifPresent but it returns void. (And for me it seems that ifPresent should not return anything other than void)

Am I misusing Optional?

Is the absence of the peek method intentional? (But at the same time Vavr's Option does provide the peek method.)

Or it was just considered not worth it?

like image 690
Denis Zavedeev Avatar asked Dec 02 '18 13:12

Denis Zavedeev


2 Answers

There is already the Optional::ifPresent method that accepts a Consumer.

In Java 8, the only way is to use Optional::map, to map the entity to itself and use it as peek method:

return repository.findById(id)
                 .map(u -> {
                     logger.debug("Found user = {} with id = {}", u, id)
                     return u;
                 })
                 .orElseThrow(() -> new UserNotFoundException("id = " + id));

... which shall be simplified implementing an own peek method:

<T> UnaryOperator<T> peek(Consumer<T> consumer) {
    return t -> {
        consumer.accept(t);
        return t;
    };
}

... and used comfortably with Optional:

return repository.findById(id)
                 .map(this.peek(logger.debug("Found user = {} with id = {}", u, id)))
                 .orElseThrow(() -> new UserNotFoundException("id = " + id));
like image 188
Nikolas Charalambidis Avatar answered Oct 22 '22 11:10

Nikolas Charalambidis


Well, only the designers could answer you the "exact" details as to why there was no peek method for Optionals.

So, for now, you're stuck with using isPresent() which actually seems just fine in my view:

if (userOptional.isPresent()) 
    logger.debug("Found user = {} with id = {}", userOptional.get(), id);

or you could consider the suggested answers on the linked page if you want it as part of the pipeline.

btw, given the new stream method as of JDK9 you could do:

return repository.findById(id) // Optional<User>
                 .stream()  // Stream<User>
                 .peek(u -> logger.debug("Found user = {} by id = {}", u, id)) // Stream<User>
                 .findFirst() // Optional<User>
                 .orElseThrow(() -> new UserNotFoundException("id = " + id))

see this answer for a similar example.

like image 4
Ousmane D. Avatar answered Oct 22 '22 12:10

Ousmane D.