过滤Java 8 Stream ,通常使用findFirst()findAny()来获取在过滤器中幸存的元素。 但这可能并不能真正实现您的意思,并且可能会出现一些细微的错误。

那么

从我们的Javadoc( 此处和此处 )可以看出,这两个方法都从流中返回任意元素-除非流具有遇到顺序 ,在这种情况下, findFirst()返回第一个元素。 简单。

一个简单的示例如下所示:

public Optional<Customer> findCustomer(String customerId) {return customers.stream().filter(customer -> customer.getId().equals(customerId)).findFirst();
}

当然,这只是旧的for-each-loop的漂亮版本:

public Optional<Customer> findCustomer(String customerId) {for (Customer customer : customers)if (customer.getId().equals(customerId))return Optional.of(customer);return Optional.empty();
}

但是,这两种变体都包含相同的潜在错误:它们是基于隐含的假设而建立的,即只有一个具有任何给定ID的客户。

现在,这可能是一个非常合理的假设。 也许这是一个已知的不变式,由系统的专用部分保护,并由其他人员依赖。 在那种情况下,这是完全可以的。

通常,代码依赖于唯一的匹配元素,但是没有做任何断言。

但是,在许多情况下,我并不是在野外看到的。 也许客户只是从外部来源加载的,这些来源无法保证其ID的唯一性。 也许现有的错误允许两本书具有相同的ISBN。 也许搜索词允许出乎意料的许多意外匹配(有人说过正则表达式吗?)。

通常,代码的正确性取决于以下假设:存在与条件匹配的唯一元素,但它不执行或声明此条件。

更糟糕的是,不当行为完全是由数据驱动的,可能会在测试期间将其隐藏。 除非我们牢记这种情况,否则我们可能会完全忽略它,直到它在生产中显现出来为止。

更糟糕的是,它默默地失败了! 如果只有一个这样的元素的假设被证明是错误的,我们将不会直接注意到这一点。 取而代之的是,系统会在观察到影响并查明原因之前巧妙地表现出一段时间。

因此,当然, findFirst()findAny()本身并没有错。 但是,使用它们很容易导致建模域逻辑中的错误。

Steven Depolo在CC-BY 2.0下发布

快速失败

因此,让我们解决这个问题! 假设我们非常确定最多有一个匹配元素,如果没有,我们希望代码快速失败 。 通过循环,我们必须管理一些难看的状态,它看起来如下:

public Optional<Customer> findOnlyCustomer(String customerId) {boolean foundCustomer = false;Customer resultCustomer = null;for (Customer customer : customers)if (customer.getId().equals(customerId))if (!foundCustomer) {foundCustomer = true;resultCustomer = customer;} else {throw new DuplicateCustomerException();}return foundCustomer? Optional.of(resultCustomer): Optional.empty();
}

现在,流为我们提供了一种更好的方法。 我们可以使用经常被忽略的reduce, 文档中对此说 :

使用关联累加函数对此流的元素进行归约 ,并返回一个Optional描述归约值(如果有)。 这等效于:

流减少

boolean foundAny = false;
T result = null;
for (T element : this stream) {if (!foundAny) {foundAny = true;result = element;}elseresult = accumulator.apply(result, element);
}
return foundAny ? Optional.of(result) : Optional.empty();

但不限于顺序执行。

看起来不像上面的循环吗?! 疯狂的巧合...

因此,我们需要的是一个累加器,该累加器会在调用后立即抛出所需的异常:

public Optional<Customer> findOnlyCustomerWithId_manualException(String customerId) {return customers.stream().filter(customer -> customer.getId().equals(customerId)).reduce((element, otherElement) -> {throw new DuplicateCustomerException();});
}

这看起来有些奇怪,但确实可以满足我们的要求。 为了使其更具可读性,我们应该将其放入Stream实用工具类中,并为其命名一个漂亮的名称:

public static <T> BinaryOperator<T> toOnlyElement() {return toOnlyElementThrowing(IllegalArgumentException::new);
}public static <T, E extends RuntimeException> BinaryOperator<T>
toOnlyElementThrowing(Supplier<E> exception) {return (element, otherElement) -> {throw exception.get();};
}

现在我们可以这样称呼它:

// if a generic exception is fine
public Optional<Customer> findOnlyCustomer(String customerId) {return customers.stream().filter(customer -> customer.getId().equals(customerId)).reduce(toOnlyElement());
}// if we want a specific exception
public Optional<Customer> findOnlyCustomer(String customerId) {return customers.stream().filter(customer -> customer.getId().equals(customerId)).reduce(toOnlyElementThrowing(DuplicateCustomerException::new));
}

目的显示代码如何?

这将实现整个流。

应该注意的是,与findFirst()findAny() ,这当然不是短路操作 ,它将实现整个流。 也就是说,如果确实只有一个元素。 当然,一旦遇到第二个元素,处理就会停止。

反射

我们已经看到findFirst()findAny()如何不足以表示流中最多剩余一个元素的假设。 如果我们要表达该假设,并确保在违反代码时代码快速失败,则需要reduce(toOnlyElement())

  • 您可以在GitHub上找到代码并随意使用-它在公共领域。

首先感谢Boris Terzic使我意识到这种意图不匹配。

翻译自: https://www.javacodegeeks.com/2016/02/beware-findfirst-findany.html

当心findFirst()和findAny()相关推荐

  1. findfirst_当心findFirst()和findAny()

    findfirst 过滤Java 8 Stream ,通常使用findFirst()或findAny()来获取在过滤器中幸存的元素. 但这可能并不能真正实现您的意思,并且可能会出现一些细微的错误. 那 ...

  2. Baeldung Java 周评 | 第一百零五弹(关键词:如果 Java 是今天设计的、内容丰富的 Spring 会议、JPA 测试用例模版、高性能 Java 持久化、自动化订购午餐、前端五强)

    开篇词 尤金的第 105 篇 Java 周评,诞生了! Spring 以及 Java 相关 如果 Java 是今天设计的:可同步接口 [jooq.org] 关于 Java 中 "可能是什么& ...

  3. findAny和findFirst区别

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/huanghanqian/article ...

  4. stream filter 用法_JDK1.8新特性Stream和Collectors19个常用示例总结

    关于Stream和Collectors的用法,这应该是总结的最全的文章了,大家可以收藏一下. 一:简介 java.util.Stream 表示能应用在一组元素上一次执行的操作序列.Stream 操作分 ...

  5. jdk8中流的使用(二)

    三.查找和匹配 处理思路:看数据集中的某些元素是否匹配一个给定的元素 Stream API通过allMatch.anyMatch.noneMatch.findFirst和findAny方法提供了这样的 ...

  6. 玩转Java8中的 Stream 之从零认识 Stream

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:litesky www.jianshu.com/p/11c9 ...

  7. 告诉你一种精简、优化代码的方式

    来自:好好学java 相对于Java8之前的Java的相关操作简直是天差地别,Java8 的流式操作的出现,也很大程度上改变了开发者对于Java的繁琐的操作的印象,从此,Java也走向了函数式编程的道 ...

  8. Java8 新特性之流式数据处理(转)

    转自:https://www.cnblogs.com/shenlanzhizun/p/6027042.html 一. 流式处理简介 在我接触到java8流式处理的时候,我的第一感觉是流式处理让集合操作 ...

  9. 流 java_java8——使用流

    概要 流让你从外部迭代转向内部迭代.这样,你就用不着写下面这样的代码来显式地管理数据集合的迭代(外部迭代)了: List vegetarianDishes = new Arraylist<> ...

最新文章

  1. 高效搭建Storm全然分布式集群
  2. StatsD!次世代系统监控的核心
  3. oracle standby同步,ORACLE 利用rman增量备份同步standby库
  4. Linux rm命令、Linux touch命令、Linux tee命令
  5. subversion mysql_MySQL数据库之httpd+mysql+php+subversion
  6. python出租车收费_使用Python分析纽约出租车搭乘数据
  7. 熊猫烧香病毒是计算机病毒,“熊猫烧香”计算机病毒大案告破
  8. C#如何实现 ASCII码与字母的互相转换?
  9. 关于清除丢失贴图与IES文件
  10. 【精读笔记】JavaScript高级程序设计 第3章 语言基础
  11. 起底身份倒卖产业:那些被公开叫卖的人生
  12. 【C语言】计算日期差
  13. NVMe ssd加速卡和NVMe ssd硬盘的区别
  14. C语言——PAT 乙级(1002.读入一个正整数 n,计算其各位数字之和,用汉语拼音写出和的每一位数字。)
  15. 顶级域名后缀列表(转)
  16. 物联网将IT安全推向边缘
  17. 接口定义语言IDL,COM
  18. 仿QQ聊天软件(JavaFX+云端数据库)
  19. netty从入门到如坟系列(1)
  20. c 语言如何隐藏光标,如何隐藏C#中textBox控件的光标

热门文章

  1. 架构师必须掌握的各种编码:ASCII、ISO-8859-1、GB2312
  2. Shell入门(九)之字符串比较
  3. 【Android】实现页面跳转
  4. 记录程序人生2020.8.11
  5. 《白鹿原》金句摘抄(四)
  6. Servlet使用适配器模式进行增删改查案例(IDeptService.java)
  7. 使用阿里云火车票查询接口案例——CSDN博客
  8. c语言 葬礼分号,其实从C语言用分号结尾开始,就是一个悲剧了……
  9. 将字符串String str= “abc god 中国 java“ 反转每个单词 结果: “cba dog 国中
  10. redis-java客户端jedis测试