[英]Setting a boolean flag inside Java 8 Stream
我想知道从java流设置布尔标志值的最佳实践是什么。 这是我想要做的一个例子:
List<Integer> list = Arrays.asList(1,2,3,4,5);
boolean flag = false;
List<Integer> newList = list.stream()
//many other filters, flatmaps, etc...
.filter(i -> {
if(condition(i)){
flag = true;
}
return condition(i);
})
//many other filters, flatmaps, etc...
.collect(Collectors.toList());
//do some work with the new list and the flag
但是,这违反了语言限制“lambda表达式中使用的变量应该是最终的或有效的最终结果”。 我能想到一些解决方案,但我不确定哪种解决方案最好。 我的第一个解决方案是添加匹配condition
元素到列表并检查List::isEmpty
。 也可以在AtomicReference
包装flag
。
请注意,我的问题类似于这个问题 ,但我试图在最后提取一个布尔值而不是设置一个变量。
不要使用完全不相关的任务来修改生成newList
的任务。 只是用
boolean flag = list.stream().anyMatch(i -> condition(i));
其次是其他流代码。
有两个典型的反对意见
但这是两次迭代。
是的,确实如此,但是谁说迭代一次ArrayList
是个问题呢? 不要试图避免多个流操作,除非您知道您确实有一个昂贵的遍历流源,如外部文件。 如果您拥有如此昂贵的资源,那么首先将元素收集到集合中可能仍然更容易,您可以遍历两次。
但它不止一次地评估condition(…)
。
嗯,实际上它的评估比原始代码要少
.filter(i -> { if(condition(i)){ flag = true; } return condition(i); })
因为anyMatch
在第一个匹配时停止,而原始谓词在每个元素上无条件地评估condition(i)
两次。
如果在条件之前有几个中间步骤,则可以收集到中间List
List<Integer> intermediate = list.stream()
//many other filters, flatmaps, etc...
.filter(i -> condition(i))
.collect(Collectors.toList());
boolean flag = !intermediate.isEmpty();
List<Integer> newList = intermediate.stream()
//many other filters, flatmaps, etc...
.collect(Collectors.toList());
但更常见的是,中间步骤并不像乍看之下那么昂贵。 类似的中间步骤的性能特征可以根据实际终端操作在不同的流操作中变化。 因此,它可能仍然可以在飞行中充分执行这些步骤:
boolean flag = list.stream()
//many other filters, flatmaps, etc...
.anyMatch(i -> condition(i));
List<Integer> newList = list.stream()
//many other filters, flatmaps, etc...
.filter(i -> condition(i))
//many other filters, flatmaps, etc...
.collect(Collectors.toList());
如果您担心代码重复本身,您仍然可以将公共代码放入流返回实用程序方法中。
只有在非常罕见的情况下,才能进入低级API,并像在这个答案中一样窥视Stream。 如果你这样做,你不应该走Iterator
的路线,它将丢失有关内容的元信息,但使用Spliterator
:
Spliterator<Integer> sp = list.stream()
//many other filters, flatmaps, etc...
.filter(i -> condition(i))
.spliterator();
Stream.Builder<Integer> first = Stream.builder();
boolean flag = sp.tryAdvance(first);
List<Integer> newList = Stream.concat(first.build(), StreamSupport.stream(sp, false))
//many other filters, flatmaps, etc...
.collect(Collectors.toList());
请注意,在所有这些情况下,如果flag
为false
,则可以快捷方式,因为结果只能是空列表:
List<Integer> newList = !flag? Collections.emptyList():
/*
subsequent stream operation
*/;
编辑:(基于Holger的评论如下 )
我在这里只是出于历史目的而回答这个问题;)这是我尝试使用Iterator
来解决问题,尽管Spliterator
要好得多。 这个答案并非100%错误,但是当流转换为Iterator
时,支持流的分裂器的特征(即SIZED
, ORDERED
等)将丢失。 请参阅Holger关于最佳方法的精彩答案 ,只要其他替代方案和关于这是否值得付出努力的简短讨论。
如果您需要知道流管道中间是否存在过滤条件匹配,您可能需要考虑将流转换为Iterator
,检查迭代器是否具有下一个元素,将该值存储为您的标志,然后从迭代器创建一个新流,最后继续使用流管道。
在代码中:
Iterator<Whatever> iterator = list.stream()
// many other filters, flatmaps, etc...
.filter(i -> condition(i))
.iterator();
boolean flag = iterator.hasNext();
然后,从迭代器创建一个新的Stream
:
Stream<Whatever> stream = StreamSupport.stream(
Spliterators.spliteratorUnknownSize(
iterator,
Spliterator.NONNULL), // maybe Spliterator.ORDERED?
false);
最后继续使用流管道:
List<Integer> newList = stream
// many other filters, flatmaps, etc...
.collect(Collectors.toList());
现在你有newList
和flag
可以使用。
分别检查布尔标志
List<Integer> list = Arrays.asList(1,2,3,4,5);
List<Integer> newList = list.stream()
.filter(i -> condition(i))
//many other filters, flatmaps, etc...
.collect(Collectors.toList());
boolean flag = list.stream()
.filter(i -> condition(i))
.findAny()
.isPresent();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.