Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java stream: use optional filter() operations on chaining

Note: this question is not related to java.util.Optional.

When dealing with streams, I often use logic like this:

 Stream<FooBar> stream = myInitialStream();
 if (needsFilter1) stream = stream.filter(c -> whatever1());
 if (needsFilter2) stream = stream.filter(c -> whatever2());
 ...
 return stream.collect(toList());

What I am trying to achieve is converting the code above to a single-expression using chaining. I find this more readable and straight forward. Until now, the only way I found to achieve that was:

return myInitialStream()
       .filter(needsFilter1? c->whatever1() : c->true)
       .filter(needsFilter2? c->whatever2() : c->true)
       .collect(toList());

Still, this would make unnecessary calls to those trivial c->true lamdas, which might produce some performance cost when scaling up.

So my question is: is there a better way of producing chained stream expressions that include optional filtering?

UPDATE: Maybe I did not make it clear enough, but the point of my question is finding a single-expression solution. If I have to use multiple statements (to initialize a predicate, for example), I could also use the first code-block of my question which essentially does the same.

like image 470
Alkis Mavridis Avatar asked Jan 08 '19 14:01

Alkis Mavridis


People also ask

Is there optional Chaining in Java?

Introduction to the JavaScript optional chaining operator allows you to access the value of a property located deep within a chain of objects without explicitly checking if each reference in the chain is null or undefined .

What does the optional filter () method do?

The filter() method of java. util. Optional class in Java is used to filter the value of this Optional instance by matching it with the given Predicate, and then return the filtered Optional instance. If there is no value present in this Optional instance, then this method returns an empty Optional instance.

How do you add two conditions to a stream filter?

You can use the firstPredicate. and(secondPredicate) to filter results on multiple conditions. The first predicate receives each element from the stream. If the first predicate evaluates to true, then te second predicate receives the same element.

Can we have multiple filter in stream Java?

1. Overview. In this tutorial, We'll learn how to utilise stream filter() with several filter conditions (can be more than one condition). Normally, we apply a single condition to streams using filter() method with lambda and then store the results in Lists or Sets.


2 Answers

Chain the predicates according to the conditions using Predicate::and returning a new Predicate.

Predicate<FooBar> predicate = c -> whatever();

if (condition1) { predicate = predicate.and(c -> whatever1()); }
if (condition2) { predicate = predicate.and(c -> whatever2()); }

List<FooBar> dest = list.stream()
    .filter(predicate)
    .collect(Collectors.toList());

Upon an update requesting a single expression. You need a source of mapped conditions to predicates anyway. With the data structure Map<Supplier<Boolean>, Predicate<Integer>>, where a key is a Supplier of a condition deciding whether a value (Predicate<FooBar>) shall be used.

Reduce the entries of a map to a new Predicate<FooBar> using chaining these Predicates with Predicate::and, for which their Supplier<Boolean> returns true (the condition is valid).

Having a Map of the conditions:

Map<Supplier<Boolean>, Predicate<FooBar>> map = new HashMap<>();
map.put(() -> needsFilter1, c -> whatever1());
map.put(() -> needsFilter2, c -> whatever2());
...

Here is a single Stream statement:

List<Integer> dest = list
        .stream()
        .filter(map.entrySet()                            // filter with a predicate ...
                .stream()
                .filter(e -> e.getKey().get())            // .. where a condition is 'true'
                .map(Entry::getValue)                     // .. get Predicates
                .reduce(i -> true, (l, r) -> l.and(r)))   // .. reduce them using AND
        .collect(Collectors.toList());               
like image 139
Nikolas Charalambidis Avatar answered Oct 19 '22 19:10

Nikolas Charalambidis


I am a bit late with my solution, anyway I'll leave it here.

I had an idea of writing a builder to construct a complex Predicate but ended up with a class FilterCondition and a method FilterCondition.combine.

Stream.of("123", "1", "12345", "", "12", "", "2")
    .filter(FilterCondition.<String>combine(
                FilterCondition.of(() -> true, s -> s.contains("3")),
                FilterCondition.of(() -> true, s -> s.contains("2")),
                FilterCondition.of(() -> false, s -> s.isEmpty())
            ).toPredicate())
    .collect(Collectors.toList());

With the static import of FilterCondition.of and FilterCondition.combine, it would look even better.

Stream.of("123", "1", "12345", "", "12", "", "2")
    .filter(combine(
                of(() -> true, s -> s.contains("3")),
                of(() -> true, s -> s.contains("2")),
                of(() -> false, String::isEmpty)
            ).toPredicate())
    .collect(Collectors.toList());

FilterCondition<T> is basically a Predicate<T> with an extra condition for checking whether the predicate should be applied.

FilterCondition.combine takes some FilterConditions and makes up a combined one.

class FilterCondition<T> {
    private final Supplier<Boolean> filterEnabled;
    private final Predicate<T> predicate;

    private FilterCondition(Supplier<Boolean> filterEnabled, Predicate<T> predicate) {
        this.filterEnabled = filterEnabled;
        this.predicate = predicate;
    }

    public static <T> FilterCondition<T> of(Supplier<Boolean> filterEnabled, Predicate<T> predicate) {
        return new FilterCondition<>(filterEnabled, predicate);
    }

    @SafeVarargs
    public static <T> FilterCondition<T> combine(FilterCondition<T>... conditions) {
        return new FilterCondition<>(
                () -> true,
                Arrays.stream(conditions).filter(i -> i.filterEnabled.get()).map(i -> i.predicate).reduce(Predicate::and).orElse(t -> true)
        );
    }

    public Predicate<T> toPredicate() {
        return predicate;
    }

}
like image 4
Andrew Tobilko Avatar answered Oct 19 '22 19:10

Andrew Tobilko