在Java 8 Stream中设置布尔标志

时间:2017-07-18 17:02:11

标签: java java-8 java-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

然而,这违反了语言限制&#34; lambda表达式中使用的变量应该是最终的或有效的最终版本&#34;。我能想到一些解决方案,但我不确定哪种解决方案最好。我的第一个解决方案是添加与condition匹配的元素到列表并检查List::isEmpty。也可以将flag包裹在AtomicReference

请注意,我的问题与此question类似,但我试图在最后提取布尔值而不是设置变量。

3 个答案:

答案 0 :(得分:8)

不要将使用完全不相关的任务生成newList的任务变得污秽。只需使用

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

后跟其他流代码。

有两个典型的反对意见

  1. 但这是两次迭代。

    是的,确实如此,但谁说重复ArrayList两次是个问题?不要试图避免多个流操作,除非您知道您确实有一个昂贵的遍历流源,如外部文件。如果您拥有如此昂贵的资源,首先可能更容易将元素收集到集合中,您可以遍历两次。

  2. 但它不止一次评估condition(…)

    嗯,实际上它的评估价格低于原始代码

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

    anyMatch在第一场比赛时停止,而原始谓词在每个元素上无条件地评估condition(i)两次。

  3. 如果在条件之前有几个中间步骤,您可以收集到中间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,并像this answer一样窥视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());
    

    请注意,在所有这些情况下,如果flagfalse,您可以快捷方式,因为结果只能是一个空列表:

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

答案 1 :(得分:2)

编辑(基于Holger's comment below

我在这里只是出于历史目的而回答这个问题;)这是我尝试用Iterator来解决问题,尽管Spliterator要好得多。这个答案不是100%错误,但是当流转换为SIZED时,支持流的分裂器的特征(即ORDEREDIterator等)将丢失。请参阅Holger's awesome answer了解最佳方法,只要有其他替代方案,并简要讨论是否值得付出努力。

如果您需要知道流管道的中间中的过滤条件是否匹配,您可能需要考虑将流转换为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());

现在您可以使用newListflag

答案 2 :(得分:1)

单独检查布尔标志

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