【问题标题】:Why does Optional not provide a peek method?为什么 Optional 不提供 peek 方法?
【发布时间】:2018-12-02 13:40:14
【问题描述】:

我很想知道为什么 Java 的 Optional 没有提供类似于 Stream's onepeek 方法。

Stream 接口的peek 方法javadoc 状态:

  • @apiNote 此方法的存在主要是为了支持调试,您希望在元素流过管道中的某个点时查看它们

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

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

也可以这样做(见answer):

@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 的一种黑客攻击,而不是干净的用法。

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

也可以使用ifPresent 执行相同的操作,但它会返回void。 (对我来说,ifPresent 似乎不应该返回 void 以外的任何东西)

我是不是在滥用Optional

peek 方法的缺失是故意的吗? (但同时 Vavr 的 Option 确实提供了 peek 方法。)

或者只是被认为不值得?

【问题讨论】:

  • 可能是因为它的设计者认为不值得,拥有ifPresent() 就足够了。
  • 如果你使用 User user = repository.findById().orElseThrow(...); logger.debug("...", user, id); return user 而不是你的构造,我看不出可读性有任何降低 - 只要三行,如果值存在,就会被记录下来。如果这是用例,难怪设计师认为这是多余的。
  • 使用 java9 你可以做repository.findById(id).stream().peek(...).findAny().orElseThrow(...),但它仍然是完成你想要的事情的一种黑客方式。这个问题绝对值得一票,我也希望得到正确的答案

标签: java lambda java-8 optional java-9


【解决方案1】:

已经有接受ConsumerOptional::ifPresent 方法。

在 Java 8 中,唯一的方法是使用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));

【讨论】:

    【解决方案2】:

    好吧,只有设计人员才能回答您“确切”的细节,说明为什么没有用于 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))
    

    see this answer for a similar example.

    【讨论】:

    • 使用 ifPresent() 会更优雅:无需调用 get()。
    • 另一种编写最后一个 sn-p 的方法(即使在 Java 8 中)是 return repository.findById(id).flatMap(u -&gt; Stream.of(u).peek(u2 -&gt; logger.debug(...)).findFirst()).orElseThrow(...);
    • @FedericoPeraltaSchaffner 有点难以阅读,但这是一个很好的呐喊!。
    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-08
      • 2012-04-23
      • 1970-01-01
      • 2017-10-09
      • 2014-05-31
      • 2019-10-09
      相关资源
      最近更新 更多