Set boolean flag in Java 8 stream
I wonder what is the best practice for setting boolean flag values from Java streams Here is an example 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,etc... .collect(Collectors.toList()); //do some work with the new list and the flag
However, this violates the language restriction that "variables used in lambda expressions should be final or valid final results" I can think of some solutions, but I'm not sure which one is the best My first solution is to add elements that match the criteria to the list and check the list:: isempty You can also wrap flags in atomicreference
Note that my question is similar to this question, but I try to extract a Boolean value at the end instead of setting a variable
Solution
Do not use completely unrelated tasks to modify the tasks that generate NEWLIST Just use
boolean flag = list.stream().anyMatch(i -> condition(i));
The second is other stream code
There are two typical objections
>But these are two iterations
Yes, that's true, but who says iterating through ArrayList once is a problem? Don't try to avoid multiple stream operations unless you know you do have an expensive traversal stream source, such as an external file If you have such expensive resources, it may still be easier to collect elements into the collection first. You can traverse it twice. > But it evaluates conditions more than once (...)
Well, in fact, it has less evaluation than the original code
.filter(i -> { if(condition(i)){ flag = true; } return condition(i); })
Because anymatch stops on the first match, the original predicate unconditionally evaluates condition (I) twice on each element
If there are several intermediate steps before the condition, they can be collected in the intermediate list
List<Integer> intermediate = list.stream() //many other filters,etc... .filter(i -> condition(i)) .collect(Collectors.toList()); boolean flag = !intermediate.isEmpty(); List<Integer> newList = intermediate.stream() //many other filters,etc... .collect(Collectors.toList());
But more often, intermediate steps are not as expensive as they seem at first glance The performance characteristics of similar intermediate steps can vary in different flow operations according to the actual terminal operation Therefore, it may still be able to fully perform these steps in flight:
boolean flag = list.stream() //many other filters,etc... .anyMatch(i -> condition(i)); List<Integer> newList = list.stream() //many other filters,etc... .filter(i -> condition(i)) //many other filters,etc... .collect(Collectors.toList());
If you are worried about code duplication itself, you can still put public code into the stream return utility method
Only in very rare cases, it is worthwhile to enter the low-level API and peep into the stream like this answer If you do this, you should not take the route of iterator. It will lose meta information about the content, but use splitter:
Spliterator<Integer> sp = list.stream() //many other filters,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,etc... .collect(Collectors.toList());
Note that in all these cases, if the flag is false, the shortcut can be used, because the result can only be an empty list:
List<Integer> newList = !flag? Collections.emptyList(): /* subsequent stream operation */;