【问题标题】:Java Streams verify that there is exactly one resultJava Streams 验证是否只有一个结果
【发布时间】:2021-03-09 13:36:43
【问题描述】:

通常,使用列表我会遇到如下情况(假设用户包含 ID 和名称):

List<User> users = loadFromDb();
List<User> lookingForIdResultList = user.stream()
.filter( u -> u.getId() == 1234)
.collect(Collectors.toList())

if(lookingForIdResultList.size() == 0) throw UserNotFoundException()
if(lookingForIdResultList.size() > 1) throw MultipleIdsNotPermittedExcpetion()

User searchedUser = lookingForIdResultList.get(0);
System.out.println("Username is "+ searchedUser.getName())

我问,是否有办法使用 java 流 api 缩短验证?

这只是一个过于简单的例子,问题很笼统。我不想从 db 加载用户。

坦克!

【问题讨论】:

  • 为什么不将 id 传递给数据库查询,只检查返回结果的数量?为什么会有多个具有相同 id 的用户呢?您说您已将其用作示例,因此如果问题更普遍:它是否总是涉及从数据库加载数据?
  • 这个特殊的例子散发着XY Problem 的味道,因为loadFromDb 可能应该接收一个参数来放入where 子句并预先过滤用户列表。
  • 即使有办法缩短它,我不确定它是否仍然是一个可读的解决方案。
  • 你完全正确。在这种特殊情况下,有更好的解决方案,但正如@Thomas 所提到的,这只是一个过于简化的例子,问题更普遍。不,数据不仅来自数据库,有时还来自国外 api 或其他内部计算。问题只是关于验证结果。

标签: java lambda stream


【解决方案1】:

如果您想在代码中进行验证,那么它不会变得更短,但您可以使代码更具可读性。

一个小的优化是使用count()而不是collect()

users.stream().filter(...).count();

一般来说,我会构建一些辅助函数来完成这项工作,这样您的代码就会看起来更干净、更易读:

List<User> users = loadFromDb();
validateUsers(users); // throws exception

validateUsers 方法可以进一步抽象,如果您在多个地方需要类似的功能:

static void validateUsers(List<User> users) {
  validateListHasExactlyOne(users, user -> user.getId() == 1234);
}

static <T> void validateListHasExactlyOne(List<T> list, Predicate<T> pred) {
  long count = list.stream().filter(pred).count();
  if(count == 0) throw ...
  if(count > 1) throw ...
}

【讨论】:

    【解决方案2】:

    Guava 库有收集器 toOptional()onlyElement() 用于这个用例:

    // throws exception if there is not exactly 1 matching user
    User searchedUser = user.stream()
        .filter( u -> u.getId() == 1234)
        .collect(MoreCollectors.onlyElement());
    

    https://www.javadoc.io/doc/com.google.guava/guava/28.0-jre/com/google/common/collect/MoreCollectors.html#onlyElement--

    【讨论】:

    • 这就是我要找的,谢谢!
    【解决方案3】:

    请注意,流在这里可能并不总是最好的选择。一个普通的旧 for 循环可能更容易读写,也更快(至少如果你不使用parallelStream())。

    static <T> T exactMatch(List<T> list, Predicate<T> predicate) {
      T result = null;
      for(T element : list) {
        if( predicate.test(element) ) {
          if(result != null) throw new DuplicateException(); //or some other exception 
          result = element;
        }
      }
      if( result == null ) throw new NoSuchElementException();
      return result;
    }
    

    这看起来更长,但它有一些优点:

    • 它检查重复项并仅使用一次迭代返回单个结果(在流中计数结果,然后获取一个需要 2 次迭代)
    • 不需要中间列表,即不需要额外的内存

    但有一个缺点:它将是单线程的。如果需要并行化,最好使用parallelStream()

    如果您还需要能够指定异常,您可以尝试这样的事情(特定异常要么未经检查,要么继承自基本异常):

    static <T> T exactMatch(List<T> list, Predicate<T> predicate, 
                            Supplier<D extends RuntimeException> duplicateSupplier, 
                            Supplier<N extends NoSuchElementException> noElementSupplier) {
    
       //same as above but using "throw duplicateSupplier.get()" etc.
    }
    

    您还可以使用标准异常进行重载,例如

    static <T> T exactMatch(List<T> list, Predicate<T> predicate) {
      return exactMatch(list, predicate, DuplicateException::new, NoSuchElementException::new);
    }
    

    【讨论】:

      猜你喜欢
      • 2017-09-09
      • 1970-01-01
      • 2018-03-25
      • 2010-10-02
      • 2019-01-02
      • 1970-01-01
      • 2019-07-08
      • 2017-10-02
      • 1970-01-01
      相关资源
      最近更新 更多