Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filtering collection stream inside collection stream before returning

Background information

I've got the following classes:

Insurance

public class Insurance {
    ...
}

Customer

public class Customer {
    private List<Insurance> insurances;

    public List<Insurance> getInsurances() {
        return insurances;
    }
    ...
}

CustomerRegistry

public class CustomerRegistry {
    private List<Customer> customers;
    ...
}

as well as this helper method, which reduces a List<Predicate<T>> into a single Predicate<T>:

public Predicate<T> reducePredicates(List<Predicate<T>> predicates) {
    return predicates.stream()
                     .reduce(Predicate::and)
                     .orElse(p -> true);
}

The problem

What I want to do is get a list of insurances that match a list of filters, belonging to customers that match a list of filters. If this is unclear, the below code will hopefully clarify.

Method is inside CustomerRegistry class above.

public List<Insurance> findInsurances(List<Predicate<Customer>> cusPredicates,
List<Predicate<Insurance>> insPredicates) {

    List<Insurance> matches = new LinkedList<>();
    customers.stream()
             .filter(reducePredicates(cusPredicates)
             .forEach(cus -> cus.getInsurances()
                               .stream()
                               .filter(reducePredicates(insPredicates))
                               .forEach(cus -> matches.add(cus)))
    return matches;
}

Is there a way to do this without the matches list? Can I perform some sort of reduction, so that the matching insurances are returned directly (i.e. not being added to a temporary collection like matches)?

like image 707
Martin M J Avatar asked May 12 '15 16:05

Martin M J


People also ask

How will you run a filter on a collection?

You can filter Java Collections like List, Set or Map in Java 8 by using the filter() method of the Stream class. You first need to obtain a stream from Collection by calling stream() method and then you can use the filter() method, which takes a Predicate as the only argument.

What is stream filtering?

A filter stream is constructed on another stream (the underlying stream). The read method in a readable filter stream reads input from the underlying stream, filters it, and passes on the filtered data to the caller.

How do you filter a list of objects using a stream?

Java stream provides a method filter() to filter stream elements on the basis of given predicate. Suppose you want to get only even elements of your list then you can do this easily with the help of filter method. This method takes predicate as an argument and returns a stream of consisting of resulted elements.

Does stream filter keep order?

If you have an ordered stream and perform operations which guarantee to maintain the order, it doesn't matter whether the stream is processed in parallel or sequential; the implementation will maintain the order.


1 Answers

Use flatMap():

customers.stream()
         .filter(reducePredicates(cusPredicates))
         .flatMap(cus -> cus.getInsurances().stream())
         .filter(reducePredicates(insPredicates))
         .collect(Collectors.toList())

Or better, to avoid reducing the predicates over and over again:

Predicate<Customer> customerPredicate = reducePredicates(cusPredicates);
Predicate<Customer> insurancePredicate = reducePredicates(insPredicates);
List<Insurance> = 
    customers.stream()
             .filter(customerPredicate)
             .flatMap(cus -> cus.getInsurances().stream())
             .filter(insurancePredicate)
             .collect(Collectors.toList())
like image 78
JB Nizet Avatar answered Oct 19 '22 08:10

JB Nizet