7

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.

Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
Alkis Mavridis
  • 1,090
  • 12
  • 28
  • is either `needsFilter1` or `needsFilter2` always guaranteed to be true? – Ousmane D. Jan 08 '19 at 14:28
  • No, these conditions are not related to each other, they can be whatever. Also, the filter operations that I need to perform are not always two. I just listed two to keep the post shorter, but could be more. I am searching for a pattern that would work no matter how many filterings you want to perform. – Alkis Mavridis Jan 08 '19 at 14:29
  • You could go with something like public static Predicate conditionalFilter(boolean condition, Predicate filter) { return condition ? filter : o -> true; } The method is generic (does not have to be rewritten all the time) and could be static-imported. – Michal Jan 08 '19 at 15:15
  • @Michal this is for sure more elegant as a syntax, but still, the o->true is being unnecessary called. – Alkis Mavridis Jan 08 '19 at 15:18
  • How do you determine whether or not the filter is needed? Can the filters themselves be aware and simply return true if not needed? – NickJ Jan 08 '19 at 15:30
  • 1
    You could do `.filter(c->(needsFilter1 && whatever1())` which is shorter and more readable than your ternary expression, and hope the JIT will realise that `needsFilter1` is a constant (or unchanging) expression and optimise it out. – Klitos Kyriacou Jan 08 '19 at 15:36
  • @KlitosKyriacou thanks for the answer. This has indeed much more elegant syntax than my second solution, but the performance consideration still remains, might be even a bit worse if the JIT cannot optimize that extra AND instruction... What I really need if a function that would return the stream unchanged, or a new stream that is the old one filtered by the "whatever" condition. Looks like this is not possible... – Alkis Mavridis Jan 08 '19 at 15:42
  • to be clear, are you trying to avoid multiple if blocks? or multiple filters? @AlkisMavridis – Ryuzaki L Jan 08 '19 at 16:14
  • I am trying to avoid multiple statements. I try to perform optional filtering within a chained stream expression, but without unnecessary trivial calls to o->true. – Alkis Mavridis Jan 08 '19 at 16:22
  • In my opinion the performance is more influenced by the streaming and collecting rather then by repeated calls of c -> true. At the same time, the streaming / collecting cannot be avoided for the requirement 'one chained call' and 'Java streamimg API'. – Michal Jan 08 '19 at 16:25
  • 2
    @KlitosKyriacou If you do `.filter(c->(needsFilter1 && whatever1())` and `needsFilter1` is `false`, you'd end up with a predicate that returns `false` for all elements of the stream. Clearly, this is wrong, because OP wants to skip filtering in this case. – fps Jan 08 '19 at 16:32
  • 2
    @FedericoPeraltaSchaffner good catch! I meant `.filter(c->(!needsFilter1 || whatever1())`. – Klitos Kyriacou Jan 08 '19 at 16:35
  • @KlitosKyriacou in which case the JIT would have to optimize one NOT + one OR instruction inside the lamda... Maybe it the ternary is preferable, after all, since its evalutaion happens once, outside of lamda.. – Alkis Mavridis Jan 08 '19 at 16:38
  • @AlkisMavridis Do you have some way to match `boolean` values and predicates? I mean, do you have them in some sort of structure? Or are they just variables that you use based on some name pattern? And how many pairs are we talking about here? – fps Jan 08 '19 at 16:40
  • @FedericoPeraltaSchaffner unfortunatelly not. All conditions and filters are independent from each other. Actually my post is a template of situations that I often find myself. I was hopping to find a general pattern for that, but it looks like there is none... – Alkis Mavridis Jan 08 '19 at 16:47
  • By the way, worrying about the overhead of a call to `.filter(c->true)` is a bit academic when you consider that `.collect(toList())` is O(N log N). – Klitos Kyriacou Jan 08 '19 at 17:31

2 Answers2

11

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());               
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
  • Why would not want to have the predicate created this way? I mean single expression statements are fine for simple actions but here you attempt to create a predicate that changes dynamically based on various conditions. Not only using single expression statements effectively hides the multiple unneeded calls, but furthermore it also makes the whole thing a bit more unreadable. Why don't create a method that accepts the flags and returns the constructed predicate? If not this way you'll also achieve better re-usability (assuming that you use the said predicate elsewhere). – akortex Jan 08 '19 at 15:03
  • 1
    As mentioned, I use this kind of logic very often with different combination of filters. Having to every time write helper methods with flags that return the predicate would not help me. If there is no way to have a single-expression solution, I would go with the first code block of my question, which is fine, but not ideal. In any case, my question is whether there is a way to achieve this. If this is a good idea or not is a rather subjective matter. – Alkis Mavridis Jan 08 '19 at 15:09
  • @AlkisMavridis: Not fully, yet this is a correct approach which doesn't leave the `Stream` opened and avoids temporal coupling between the `Stream::filter` calls. You want to construct a predicate to be applied once, here I provided *one* of those ways. – Nikolas Charalambidis Jan 08 '19 at 16:23
  • @Nikolas What I ask for is a single-expression solution. I am sorry if I have not stated that clear enough, but this is precisely what my question is about. Your solution is a nice approach and thank you for posting it, but it does not answer my question. Maybe what I ask for is simply not possible in java-stream API, which is maybe the answer. – Alkis Mavridis Jan 08 '19 at 16:33
  • 2
    @Nikolas thanks a lot! This now is genius idea. So as I understand your approach, I could work with maps or collections to build my stream. Those would provide the conditions and filters to be used. In the worst case, I would have the overhead of one o->true filtering, but this would be just one. Also the filter() part could go in an utils function that would be written only once and used everywhere. Looks great to me! – Alkis Mavridis Jan 08 '19 at 17:09
  • Instead of `.map(Entry::getValue).reduce(gobbledygook)` can't you just do `.allMatch(Entry::getValue)`? – Klitos Kyriacou Jan 08 '19 at 17:25
  • @KlitosKyriacou: Nope, the idea of reducing `Predicate`s is to chain them using `Predicate::and` method to result **one** predicate to be used in the `Stream::filter`. The method `Stream::allMatch` returns `boolean` which is not helpful here. – Nikolas Charalambidis Jan 08 '19 at 17:27
  • `Map` is a very inconvenient collection to fill in in a one-line manner (Java 9's `Map.of` simplified it a bit, though). – Andrew Tobilko Jan 08 '19 at 18:21
  • 1
    I think you `filter` stuff should be moved into a method because it looks cumbersome and unmaintainable now. Anyway, I like the idea, pretty similar to mine. – Andrew Tobilko Jan 08 '19 at 18:22
  • 1
    @AndrewTobilko: Yes Andrew, for sake of brevity and the OP's request, I included such a single-statement approach. I prefer to use objects even for the smallest procedures. – Nikolas Charalambidis Jan 08 '19 at 18:28
  • 3
    Instead of `Supplier` you can use `BooleanSupplier`. Further, instead of `.reduce(i -> true, (l, r) -> l.and(r))`, you can use `.reduce(Predicate::and).orElse(i -> true)` which saves you from combining existing predicates with `i->true` (in fact, you’ll get the original predicate if only one remains, then). – Holger Jan 09 '19 at 10:44
4

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;
    }

}
Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
  • 2
    Shall `FilterCondition` implement `Predicate`? Cons: The method `get` would be implemented against an interface contact. Flexible using variety of constructors. The object would **be** a predicate, not just provide it. What do you think? Have my +1 for a nice way :)) – Nikolas Charalambidis Jan 08 '19 at 18:25
  • @Nikolas what kind of `Predicate`'s constructors did you imply? – Andrew Tobilko Jan 09 '19 at 10:33
  • @AndrewTobilko Noone. Predicate is a functional interface. I meant that your custom class would be flexible using more constructors. – Nikolas Charalambidis Jan 09 '19 at 12:25
  • @Nikolas what constructors? Could you give an example? – Andrew Tobilko Jan 09 '19 at 12:26
  • I think what @Nikolas means is that you can generate a Predicate instance using the lambda syntax. It is technically not a constructor, but it acts similarly to a constructor. – Alkis Mavridis Jan 09 '19 at 15:17
  • @AndrewTobilko By the way, your solution is precisely what I was looking for. I find its logic very similar to the Nikolas answer above. Also, building a class with methods such as combine / toPredicate is a really great way to implement this kind of logic in an elegant and reusable way. Thanks! – Alkis Mavridis Jan 09 '19 at 15:20
  • FilterCondition looks like the Builder pattern, but with a different syntax to the way most Java builders work. – Klitos Kyriacou Jan 09 '19 at 15:22
  • @KlitosKyriacou Yes, but I think `combine` is a good replacement for the `builder().and(...).and(...).build()` style – Andrew Tobilko Jan 09 '19 at 15:25
  • @AlkisMavridis to represent a `FilterCondition` by a lambda, it should be a functional interface (an interface with a single method), but it can't be such since it holds the state of 2 fields. – Andrew Tobilko Jan 09 '19 at 15:29
  • @AndrewTobilko, sorry, I misread... Sure, for your FilterCondition class is very good as it is. You could not replace it with a functional interface. – Alkis Mavridis Jan 09 '19 at 16:28