Java 8 Lambdas - 位与操作

16

我目前正在解决在Java 8中使用Lambda转换位运算的for循环问题。

给定一组复杂的条目,需要循环遍历所有条目并调用给定方法(该方法返回布尔值)。之后返回结果。

换句话说,我需要在所有条目上调用该方法并存储结果。 其原因是每个条目独立执行复杂操作,必须执行。 最终结果是一系列结果的组合。

代码片段:

boolean areAllSuccessful = true;
for (SomeEntry entry : entries) {
     areAllSuccessful = areAllSuccessful & entry.doComplexAction(); // keep calling the method on other entries regardless of the result.
}

return areAllSuccessful;

问题在于Java 8中的Lambda函数通常执行短路操作(一旦检测到假条目,“循环”中断并返回假结果)。

到目前为止,我最好的解决方案是使用map / filter / count组合:

return entries
       .stream()
       .map(entry -> entry.doComplexAction())
       .filter(result -> result == false)
       .count() > 0

有没有更聪明/更清洁的方法来做这件事?

谢谢!


7
我认为你的用例更适合使用传统的“for”循环而不是流。实际上,试图让所有东西都基于流是不可取的。如果当前方式有效,那就保持原样吧。 - fps
19
仅仅是一条评论:isSuccessful() 是一个带有副作用且执行许多操作的方法,这是一个非常糟糕的名称。你应该1)在问题中明确指出该方法具有副作用,因此需要在每个条目上调用;2)将该方法重命名为 doComplexActions() 等更合适的名称。 - walen
4
如果需要流,就不要使用副作用;如果需要副作用,就不要使用流。其他做法都可能会带来麻烦。 - user11153
2
被调用的方法具有副作用,应该更加突出地书写(加粗、闪烁、在标题中等等)。 - Roland
1
不要使用位运算来避免短路。你可以轻松地只是反转顺序(entry.isSuccessful() && areAllSuccessful)来强制执行方法。不要用螺丝刀敲钉子,这只会让大家困惑。此外,不要在超出屏幕并且开发人员无法阅读的行末添加长注释。请将其放在所注释的行之前的单独一行上。 - jpmc26
显示剩余3条评论
3个回答

29

那不应该看起来像这样:

boolean areAllSuccessful = entries.stream()
   .map(entry -> entry.isSuccessful())
   .reduce(Boolean.TRUE, Boolean::logicalAnd);

15
然后等待下一个开发者看到代码并说:“嘿,这看起来像是allMatch的完美任务”。 - Holger
1
@Holger 我也不小心想到了这个问题...看着一个构造函数有3个字段,只有一个是final的。我的第一反应是完全删除那个final...在这里也差不多。可能需要一个代码注释。 - Eugene
2
@Holger Bohemian刚刚发布了一个使用allMatch的答案:https://dev59.com/WVgQ5IYBdhLWcg3wvGm4#42277670/ - Ismael Miguel
3
我觉得有 Boolean::logicalAnd,但仍然有 entry -> entry.isSuccessful() 有点令人不安。后者不能写成 SomeEntry::isSuccessful 吗? - Olivier Grégoire

21

最简洁和高效的方法是使用一个方法引用,并与allMatch()一起使用。

return entries.stream().allMatch(SomeEntry::isSuccessful);

如果您有成千上万的元素,请考虑使用parallelStream()

这不会处理每个元素(它在第一个 false 处返回),所以如果您的isSuccessful()方法具有副作用,则它是一个糟糕的名称,您应该将其重命名或重构代码以执行process()(或类似)方法中的副作用,并让isSuccessful()返回结果,如果没有首先调用process()则抛出IllegalStateException异常。

如果您不进行重构,则一些开发人员(包括您自己)将调用isSuccessful()而不意识到它会“做事情”,这可能是不好的。


5
那就是重点,原帖作者不想停留在第一个错误。他在评论中这样说:“实际上我有点掩盖了问题。isSuccessful()调用背后的操作实际上是复杂的独立系统动作,应该无论彼此如何都要执行。最终结果是所有响应的组合。” - Eugene
1
无论旁注(注释)如何,这对于其他遇到此问题的人(例如我)非常有帮助。 - Rainer
3
我的回答解释了allMatch()解决方案,我的评论解释了像isSuccessful()这样的名称有多糟糕。但是你的回答两者都解释了,并且更清晰。+1。 - walen

11

如果您要使用count(),则实际上不需要map()

return !(entries
        .stream()
        .filter(entry -> !entry.isSuccessful())
        .count() > 0);
如果方法isSuccessful()没有副作用,并且你只需要知道所有条目是否都成功了,那么你可以使用allMatch()


return entries
        .stream()
        .allMatch(entry -> entry.isSuccessful());

这确实是一个短路操作,只要发现isSuccessful()false的条目,就会立即返回false,而不必消耗整个流。但您已经评论说isSuccesful()实际上意味着“执行一些复杂的操作,然后告诉我它们是否成功”,因此不适用。


1
让我困扰的是这个注释:不论结果如何,在其他条目上继续调用该方法;allMatch 会在找到第一个不匹配的元素时立即停止处理。 - Eugene
@Eugene 是的,我刚刚注意到了。不过很奇怪,因为在这个例子中,显然并不需要处理所有元素,使用按位或也是一样。 - walen
1
实际上,我已经稍微隐藏了这个问题。isSuccessful()调用背后的操作实际上是复杂的独立系统操作,应该无论彼此如何都要执行。最终结果是所有响应的组合。 - xanmcgregor
@xanmcgregor 哦,我明白了。好吧,那么我的最后一次编辑就这样了 :D 我会回到我的第一个答案! - walen

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