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
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.
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
*/;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With