Java stream: use optional filter() operations on chaining
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());
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 FilterCondition
s 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;
}
}