Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stream filter by two parameters

I have googled quite a bit, but didn't found an answer. Here is what I have:

    parentList.forEach(p -> {
        childList
                .stream()
                .filter(c -> p.id() == c.parentId())
                .<...continue working on stream...>
    });

I cannot find a way how to replace "filter" part with a Predicate as it seems that I need to pass argument to Predicate?

like image 860
lapkritinis Avatar asked Sep 14 '25 10:09

lapkritinis


1 Answers

Your problem is that you're using a different Predicate each time, because although c is the parameter to your predicate, p also varies:

final Node p;
Predicate<Node> matchesParentId = c -> p.id() == c.id();

The reason your existing code compiles OK is that p is effectively final in the scope of the forEach block, so it can be used as a final field in a Predicate within that scope, with a lifetime of one forEach iteration.

You could do:

parentList.forEach(p -> {
    childList
            .stream()
            .filter(matchesId(p))
            .<...continue working on stream...>
});

private Predicate<Node> matchesId(Node other) {
     return node -> node.id() == other.id();
}

But you won't be able to create one Predicate and reuse it as p varies.


You could write a BiPredicate and curry it into a Predicate. Unfortunately Java doesn't provide a curry method, so you have to provide your own.

private <T,U> Predicate<U> curry(BiPredicate<T,U> biPredicate, T t) {
    return u -> biPredicate.test(t, u);
} 

BiPredicate<Node,Node> nodesMatch = (a,b) -> a.id() == b.id();

parentList.forEach(p -> {
    childList
        .stream()
        .filter(curry(nodesMatch, p))
        .<...continue working on stream...>
});

This doesn't buy you all that much over and above the previous solution, but it's a bit more FP-nerdy. You're still creating a new Predicate for every p. Of course you could inline it rather than use the curry() method.

.filter(c -> nodesMatch.test(p, c))

It does mean you could have a selection of BiPredicate<Node,Node>s to plug in dynamically. If your BiPredicate were expensive to initialise, the many Predicates wrapped around it by currying would be cheap.


Or, you could map p and c into a single object, which allows you to submit the whole thing to a predicate:

Predicate<Pair<Node,Node>> nodesMatch = pair -> 
    pair.left().id() == pair.right().id();

parentList.forEach(p -> {
    childList
        .stream()
        .map(c -> new Pair<Node>( c, p))
        .filter(nodesMatch)
        .map( pair -> pair.left() )
        .<...continue working on stream...>
});

(Pair here is hypothetical, but a number of 3rd party libraries (e.g. Guava) provide one, or roll your own, or use new Node[] { c, p })

like image 180
slim Avatar answered Sep 17 '25 01:09

slim