Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 Stream: Filter, process results, then process the exclusions

In Java 8's Streams, I know how to filter a collection based on a predicate, and process the items for which the predicate was true. What I'm wondering is, if the predicate divides the collection into only two groups, is it possible through the API to filter based on the predicate, process the filtered results, then immediately chain on a call to process all elements excluded by the filter?

For instance, consider the following List:

List<Integer> intList = Arrays.asList(1,2,3,4);

Is it possible to do:

intList.stream()
    .filter(lessThanThree -> lessThanThree < 3)
    .forEach(/* process */)
    .exclusions(/* process exclusions */); //<-- obviously sudocode

Or would I have to just do the forEach process for the filtered items and then call stream() and filter() on the original list to then process the remaining items?

Thanks!

like image 577
alowellj Avatar asked May 04 '17 16:05

alowellj


2 Answers

Take a look at Collectors.partitioningBy, which receives a predicate as an argument. You must use Stream.collect to use it.

The result is a map with only two Boolean entries: for the true key, you have a List containing the elements of the stream that matched the predicate, while for the false key, you have a List containing the elements that didn't match.

Note that Collectors.partitioningBy has an overloaded version that accepts a second argument, which is a downstream collector you can use if you want each partition to be collected to something different than a list.

In your example, you could use it this way:

Map<Boolean, List<Integer>> partition = intList.stream()
    .collect(Collectors.partitioningBy(i -> i < 3));

List<Integer> lessThan = partition.get(true); // [1, 2]
List<Integer> notLessThan = partition.get(false); // [3, 4]

I would also like to highlight an important observation, taken from a comment added by user @Holger in the linked question:

Collectors.partitioningBy will always have a result for true and false in the Map, i.e. an empty list if no element fell into that group, rather than null.

This particularity has been added to jdk9 docs as an API Note (again, this was observed by user @Holger):

If a partition has no elements, its value in the result Map will be an empty List.

like image 169
fps Avatar answered Oct 06 '22 07:10

fps


There is no operation in the Stream interface to access excluded items. You can however implement a more complex predicate:

intList.stream()
    .filter(i -> {
        if (i < 3) return true;
        else {
            // Process excluded item i
            return false;
        }
    }).forEach(/* Process included item. */)

But you must be careful to keep the predicate non-interfering and stateless.

like image 45
Emil Forslund Avatar answered Oct 06 '22 07:10

Emil Forslund