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?
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));
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With