简体   繁体   English

在Java 8 Stream中设置布尔标志

[英]Setting a boolean flag inside Java 8 Stream

I'm wondering what the best practices are for setting a boolean flag value from a java stream. 我想知道从java流设置布尔标志值的最佳实践是什么。 Here is an example of what I want to do: 这是我想要做的一个例子:

    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

However, this goes against the language restriction "Variable used in lambda expression should be final or effectively final". 但是,这违反了语言限制“lambda表达式中使用的变量应该是最终的或有效的最终结果”。 There are a few solutions I can think of but I'm not sure which is best. 我能想到一些解决方案,但我不确定哪种解决方案最好。 This first solution I have is adding elements that match condition to a list and checking List::isEmpty . 我的第一个解决方案是添加匹配condition元素到列表并检查List::isEmpty One could also wrap flag in an AtomicReference . 也可以在AtomicReference包装flag

Note that my question is similar to this question , but I'm trying to extract a boolean value in the end rather than setting a variable. 请注意,我的问题类似于这个问题 ,但我试图在最后提取一个布尔值而不是设置一个变量。

Don't taint your task of producing the newList with an entirely unrelated task. 不要使用完全不相关的任务来修改生成newList的任务。 Just use 只是用

boolean flag = list.stream().anyMatch(i -> condition(i));

followed by the other stream code. 其次是其他流代码。

There are two typical objections 有两个典型的反对意见

  1. But this is iterating twice. 但这是两次迭代。

    Yes, it is, but who says that iterating an ArrayList twice is a problem? 是的,确实如此,但是谁说迭代一次ArrayList是个问题呢? Don't try to avoid multiple stream operations, unless you know that you really have an expensive-to-traverse stream source, like an external file. 不要试图避免多个流操作,除非您知道您确实有一个昂贵的遍历流源,如外部文件。 If you have such an expensive source, it might still be easier to collect the elements into a collection first, which you can traverse twice. 如果您拥有如此昂贵的资源,那么首先将元素收集到集合中可能仍然更容易,您可以遍历两次。

  2. But it's evaluating condition(…) more than once. 但它不止一次地评估condition(…)

    Well, actually it's evaluating it less than you original code 嗯,实际上它的评估比原始代码要少

     .filter(i -> { if(condition(i)){ flag = true; } return condition(i); }) 

    as anyMatch stops at the first match, while you original predicate evaluates condition(i) twice per element, unconditionally. 因为anyMatch在第一个匹配时停止,而原始谓词在每个元素上无条件地评估condition(i)两次。


If you have several intermediate steps preceding the condition, you may collect into an intermediate List like 如果在条件之前有几个中间步骤,则可以收集到中间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());

but more than often, the intermediate step aren't as expensive at it might seem at the first glance. 但更常见的是,中间步骤并不像乍看之下那么昂贵。 The performance characteristics of similar intermediate steps may vary in different stream operations, depending of the actual terminal operation. 类似的中间步骤的性能特征可以根据实际终端操作在不同的流操作中变化。 So it might still work sufficiently doing these steps on-the-fly: 因此,它可能仍然可以在飞行中充分执行这些步骤:

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());

If you worry about the code duplication itself, you can still put the common code into a stream returning utility method. 如果您担心代码重复本身,您仍然可以将公共代码放入流返回实用程序方法中。

Only in very rare cases, it pays off to go into the lowlevel API and peek into the Stream like in this answer . 只有在非常罕见的情况下,才能进入低级API,并像在这个答案中一样窥视Stream。 And if you do so, you shouldn't go the route of an Iterator which will loose meta information about the contents, but use a Spliterator : 如果你这样做,你不应该走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());

Note that in all of these cases, you could short-cut if flag is false , as the result can only be an empty list then: 请注意,在所有这些情况下,如果flagfalse ,则可以快捷方式,因为结果只能是空列表:

List<Integer> newList = !flag? Collections.emptyList():
/*
   subsequent stream operation
 */;

EDIT: (based on Holger's comment below ) 编辑:(基于Holger的评论如下

I'm letting this answer here only for historical purposes ;) This was my attempt to solve the problem with an Iterator , though a Spliterator is much better. 我在这里只是出于历史目的而回答这个问题;)这是我尝试使用Iterator来解决问题,尽管Spliterator要好得多。 This answer is not 100% wrong, however the characteristics of the spliterator that backs the stream (ie SIZED , ORDERED , etc) are lost when the stream is transformed to an Iterator . 这个答案并非100%错误,但是当流转换为Iterator时,支持流的分裂器的特征(即SIZEDORDERED等)将丢失。 Please see Holger's awesome answer for the best approach, as long as other alternatives and a brief discussion about whether this is worth the effort. 请参阅Holger关于最佳方法的精彩答案 ,只要其他替代方案和关于这是否值得付出努力的简短讨论。


If you need to know whether there has been a match on a filter condition in the middle of a stream pipeline, you might want to consider converting the stream to an Iterator , check if the iterator has a next element, store that value as your flag, then create a new stream from the iterator and finally go on with the stream pipeline. 如果您需要知道流管道中间是否存在过滤条件匹配,您可能需要考虑将流转换为Iterator ,检查迭代器是否具有下一个元素,将该值存储为您的标志,然后从迭代器创建一个新流,最后继续使用流管道。

In code: 在代码中:

Iterator<Whatever> iterator = list.stream()
    // many other filters, flatmaps, etc...
    .filter(i -> condition(i))
    .iterator();

boolean flag = iterator.hasNext();

Then, create a new Stream from the iterator: 然后,从迭代器创建一个新的Stream

Stream<Whatever> stream = StreamSupport.stream(
    Spliterators.spliteratorUnknownSize(
        iterator, 
        Spliterator.NONNULL), // maybe Spliterator.ORDERED?
    false);

And finally go on with the stream pipeline: 最后继续使用流管道:

List<Integer> newList = stream
    // many other filters, flatmaps, etc...
    .collect(Collectors.toList());

Now you have newList and flag to work with. 现在你有newListflag可以使用。

Check for the boolean flag separately 分别检查布尔标志

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM