在Java 8 Stream中设置布尔标志

mit*_*tch 4 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
Run Code Online (Sandbox Code Playgroud)

但是,这违反了语言限制"lambda表达式中使用的变量应该是最终的或有效的最终结果".我能想到一些解决方案,但我不确定哪种解决方案最好.我的第一个解决方案是添加与condition列表匹配的元素并进行检查List::isEmpty.人们还可以包裹flag一个AtomicReference.

请注意,我的问题类似于这个问题,但我试图在最后提取一个布尔值而不是设置一个变量.

Hol*_*ger 9

不要玷污你完成newList一项完全无关的任务.只是用

boolean flag = list.stream().anyMatch(i -> condition(i));
Run Code Online (Sandbox Code Playgroud)

其次是其他流代码.

有两个典型的反对意见

  1. 但这是两次迭代.

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

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

    嗯,实际上它的评估比原始代码要少

    .filter(i -> {
        if(condition(i)){
            flag = true;
        }
        return condition(i);
    })
    
    Run Code Online (Sandbox Code Playgroud)

    作为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());
Run Code Online (Sandbox Code Playgroud)

但更常见的是,中间步骤并不像乍看之下那么昂贵.类似的中间步骤的性能特征可以根据实际终端操作在不同的流操作中变化.因此,它可能仍然可以在飞行中充分执行这些步骤:

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());
Run Code Online (Sandbox Code Playgroud)

如果您担心代码重复本身,您仍然可以将公共代码放入流返回实用程序方法中.

只有在非常罕见的情况下,才能进入低级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());
Run Code Online (Sandbox Code Playgroud)

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

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