Setting boolean flag inside Java 8 Stream
I am wondering what is the best fit for setting a boolean flag value from a java stream. Here's 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 is contrary to language restrictions. "The variable used in the lambda expression must be final or effective final." There are several solutions I can think of, but I'm not sure which is better. This first solution I have is to add items matching condition
, list and check List::isEmpty
. Can also be wrapped flag
in AtomicReference
.
Please note that my question is similar to this question , but I am trying to fetch a boolean at the end rather than setting a variable.
source to share
Do not close a create task newList
with a completely unrelated task. Just use
boolean flag = list.stream().anyMatch(i -> condition(i));
followed by another stream code.
There are two typical objections
-
But this is repeated twice.
Yes it is, but who says the repetition
ArrayList
problem is a problem? Don't try to avoid multiple stream operations unless you know you actually have a stream source with an expensive stream, such as an external file. If you have such an expensive source, it's still easier to collect the items in the collection in the first place, which you can go through twice. -
But his score is
condition(โฆ)
more than once.Well, actually his score is less than the original code
.filter(i -> { if(condition(i)){ flag = true; } return condition(i); })
how it
anyMatch
stops at the first match, while the original predicate evaluatescondition(i)
twice per element, unconditionally.
If you have multiple intermediate steps preceding this condition, you can collect the intermediate List
, for example
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 often than it might seem at first glance an intermediate step, how expensive it is on it. The performance of similar intermediate steps may differ from one line to another depending on the actual operation of the terminal. Thus, it can still perform these actions quite efficiently 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 are worried about the code duplication itself, you can still put the generic code on the stream that returns the utility method.
Only on very rare occasions, it pays to go to the lowlevel API and look into the Stream like in this answer . And if you do, you shouldn't go down a route Iterator
that will lose meta information about the content, but use 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 can shorten if flag
- false
, as the result can only be an empty list:
List<Integer> newList = !flag? Collections.emptyList():
/*
subsequent stream operation
*/;
source to share
EDIT: (based on Holger's comment below )
I'm only allowing this answer for historical purposes;) This was my attempt at solving the problem with help Iterator
, although Spliterator
much better. This answer is not 100% wrong, however the characteristics of the splitter that the stream supports (i.e. SIZED
, ORDERED
etc.) are lost when the stream is converted to Iterator
. Please see Holger's awesome answer for a better approach if there are other alternatives and a short discussion on whether it's worth doing.
If you need to know if a filter condition was matched in the middle of a stream pipeline, you may need to convert the stream to Iterator
, check if the iterator has the next element, stores that value as your flag, then create a new stream from the iterator, and finally continue with flow conveyor.
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 one Stream
from the iterator:
Stream<Whatever> stream = StreamSupport.stream(
Spliterators.spliteratorUnknownSize(
iterator,
Spliterator.NONNULL), // maybe Spliterator.ORDERED?
false);
Finally, continue with the streaming pipeline:
List<Integer> newList = stream
// many other filters, flatmaps, etc...
.collect(Collectors.toList());
Now you have newList
and flag
to work with.
source to share
Check boolean checkbox 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();
source to share