简体   繁体   中英

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. 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". 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 . One could also wrap flag in an AtomicReference .

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. 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? 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.

    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.


If you have several intermediate steps preceding the condition, you may collect into an intermediate List like

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 . 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 :

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:

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

EDIT: (based on Holger's comment below )

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. 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 . 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.


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.

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<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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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