findfirst_当心findFirst()和findAny()

findfirst

那么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()本质上没有错。 但是,使用它们很容易导致建模域逻辑中的错误。

快速失败

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

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文档对此表示

执行减少有关此流的元件,使用缔合累积功能,并返回一个可选描述的缩小值,如果有的话。 这等效于:

boolean foundAny = false;
T result = null;
for (T element : this stream) {
    if (!foundAny) {
        foundAny = true;
        result = element;
    }
    else
        result = 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使我意识到这种意图不匹配。

该帖子最初发布在codefx.org上

翻译自: https://jaxenter.com/beware-of-findfirst-and-findany-123425.html

findfirst

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值