I have a list of objects which needs to be filtered with another list of functional interfaces.
For example:
List<SomeClass> originalList = .....
List<SomeClass> filteredList = originalList.stream().filter(filterList).toList();
^^^^^^^^^^^^^^^^
How do I use a list of functional interfaces here such that the final list contains objects filtered by all the filters in the list. Where
List<FilterClass> filterList = Arrays.asList(
new ClassWhichImplementsFilterClass1(),
new ClassWhichImplementsFilterClass2()
);
FilterClass.java
@FunctionalInterface
public interface FilterClass{
boolean isValid(SomeClass someClass);
}
How do I achieve this? I think I can do this by streaming the list of objects and then passing that object through each filter class in the filterList
, but is there a way to do it some other way.
CodePudding user response:
There are 2 separate issues here; I shall cover them both.
FilterClass probably shouldn't exist
The name is very strange (it's an interface; things that implement it generally are barely considered a class, the only interpretation that lets you state that they are, means everything is, and thus the presence of Class
in the name of the thing doesn't add any information). It's also pointless to make this interface; java already has it: Predicate<SomeClass>
:
class Student {
@Getter public LocalDate birthDate;
}
Predicate<Student> isAdult = s -> ChronoUnit.YEARS.between(
LocalDate.now(), s.getBirthDate()) >= 18;
Applying multiple filters
Evidently, you have a list of filters. How would you like to apply this multitude? There are 4 obvious answers:
- If ALL filters match the item, it passes, otherwise, remove it from the stream.
- If just ONE filter matches the item, it passes, otherwise, remove it from the stream.
- If ALL filters match the item, it fails and is removed, otherwise, pass the item.
- If just ONE filter matches the item, it fails and is removed. The item survives only if all filters fail to match it.
Regardless of your choice, you just.. program it. Write a new predicate that implements your choice. For example, if you want the top choice:
List<Predicate<Student>> filterList = ...;
Predicate<Student> allMatch = s -> {
for (var pred : filterList) if (!pred.test(s)) return false;
return true;
}
foo.stream().filter(allMatch).collect(....);
You may instead use the various utility methods in Predicate
itself to construct a new predicate. For example, the above can alternatively be implemented using predicate's own and
method:
List<Predicate<Student>> filterList = ...;
Predicate<Student> allMatch = s -> true;
for (var pred : filterList) allMatch = allMatch.and(pred);
Or even:
List<Predicate<Student>> filterList = ...;
Predicate<Student> allMatch = filterList.stream()
.collect(Collectors.reducing(s -> true, Predicate::and));
NB: If you insist on keeping your own FilterClass
interface for this exercise, pretty much only the first option would be available to you, unless you fully duplicate all of java.util.function.Predicate
and also add the default and
method just like Predicate has.
CodePudding user response:
There are several issues you need to consider:
Stream-operations like
filter
expect aPredicate
(i.e. it's not possible to pass intofilter
anything that is not of typePredicate
).You can't use your instance of your custom interface as a
Predicate
unless it doesn't extend aPredicate
.A Functional interface (that's the only purpose of this annotation), i.e. it has only one
abstract
method. If your instance extendsPredicate
it would inheritabstract
method test, i.e. it can't declare it ownabstract
method. You can preserveisValid()
as a default method.
If you want to leave your interface as is then you can perform validation like this:
List<Filter> filters = List.of();
List<SomeClass> originalList = List.of();
List<SomeClass> filteredList = originalList.stream()
.filter(foo -> filters.stream().anyMatch(filter -> filter.isValid(foo))) // or `.allMatch` depending of how validation should be performed
.toList(); // for Java 16 or collect(Collectors.toList())
And if you extend Predicate
your interface might look like this:
@FunctionalInterface
public interface Filter extends Predicate<SomeClass> {
default boolean isValid(SomeClass someClass) {
return test(someClass);
}
}
Then you would be able to make use of the default methods Predicate.and()
/ Predicate.or()
and chain your custom predicates:
List<Filter> filters = List.of();
Predicate<SomeClass> allMatch = filters.stream().map(Predicate.class::cast).reduce(t -> true, Predicate::and);
Predicate<SomeClass> anyMatch = filters.stream().map(Predicate.class::cast).reduce(t -> false, Predicate::or);
List<SomeClass> originalList = List.of();
List<SomeClass> filteredList = originalList.stream()
.filter(anyMatch)
.toList(); // for Java 16 or collect(Collectors.toList())