为什么 Optional 不提供 peek 方法?

Why does Optional not provide a peek method?

我很想知道为什么 Java 的 Optional does not provide a peek method similar to Stream's one

Stream 接口的 peek 方法 javadoc 指出:

  • @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

这几乎完全描述了我的用例:

@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>(参见 CrudRepository#findById))

但它无法编译,因为 Optional 上没有 peek 方法。

所以如果没有 peek 方法,上面的所有内容都会转换为:

@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));
}

也可以这样做(见):

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

并将其与 map 方法一起使用:

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

但我认为这是对 Optional.

的 hack 而不是干净的使用

从 Java 9 开始,可以将 Optional 转换为 Stream,但流没有 orElseThrow 方法(显然不应该)。

也可以使用 ifPresent 做同样的事情,但 returns void。 (对我来说 ifPresent 似乎不应该 return 除了 void 之外的任何东西)

我在滥用 Optional 吗?

peek 方法的缺失是故意的吗? (但同时 Vavr 的 Option does provide the peek 方法。)

或者只是被认为不值得?

好吧,只有设计人员才能回答 "exact" 详细信息,说明为什么 Optionals 没有 peek 方法。

所以,目前,您仍然坚持使用 isPresent(),在我看来这实际上还不错:

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

或者如果您希望将其作为管道的一部分,您可以考虑链接页面上的建议答案。

顺便说一句,鉴于 JDK9 的新 stream 方法,您可以这样做:

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))

.

已经有 Optional::ifPresent method that accepts a Consumer.

在Java8中,唯一的方法是使用Optional::map,将实体映射到自身并作为peek方法使用:

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

... 应简化实现自己的 peek 方法:

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

... 并与 Optional:

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

已经有 Optional::ifPresentOptional::isPresent 方法来记录结果。 但你可能想要一些东西。答案可能是疏忽。