为什么Optional没有提供peek方法?

9
我很好奇为什么Java的Optional没有提供类似于Streampeek方法。

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

还可以像这样做(参见答案):

@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完成相同的操作,但它返回void。(对我来说,ifPresent除了返回void之外,似乎不应该返回任何其他内容)
我是否误用了Optional

缺少peek方法是有意为之吗?(但同时Vavr的Option提供了peek方法。)

还是说这被认为不值得呢?


可能是因为设计者认为这并不值得,而且有ifPresent()已经足够了。 - JB Nizet
1
如果您使用以下代码而不是您的构造方式,我认为可读性并没有降低:User user = repository.findById().orElseThrow(...); logger.debug("...", user, id); return user。只需三行代码,如果存在该值,则会将其记录下来。如果这是使用情况,那么设计者认为它是多余的也就不足为奇了。 - RealSkeptic
使用Java9,您可以执行repository.findById(id).stream().peek(...).findAny().orElseThrow(...),但这仍然是一种达到您想要的方式的hackish方法。 这个问题绝对值得点赞,并且我也希望有一个适当的答案。 - Ilario
3个回答

8

已经有了Optional::ifPresent方法,接受一个Consumer

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

4

嗯,只有设计师才能回答为什么 Optional 没有 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))

请参阅类似示例的答案。它涉及如何查看可选值。

点击此处以查看示例答案.


4
使用 ifPresent() 更加优雅:不需要调用 get() 方法。 - JB Nizet
即使在Java 8中,编写最后一行代码的另一种方式是 return repository.findById(id).flatMap(u -> Stream.of(u).peek(u2 -> logger.debug(...)).findFirst()).orElseThrow(...); - fps
@FedericoPeraltaSchaffner 这样阅读起来可能有点困难,但是没错,这是个好建议! - Ousmane D.

1

已经有Optional::ifPresentOptional::isPresent方法记录结果。但是您可能希望有一些内联的内容。答案可能是疏忽。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接