Home > Blockchain >  Using a list of functional interfaces in java stream filter
Using a list of functional interfaces in java stream filter

Time:11-18

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 a Predicate (i.e. it's not possible to pass into filter anything that is not of type Predicate).

  • You can't use your instance of your custom interface as a Predicate unless it doesn't extend a Predicate.

  • A Functional interface (that's the only purpose of this annotation), i.e. it has only one abstract method. If your instance extends Predicate it would inherit abstract method test, i.e. it can't declare it own abstract method. You can preserve isValid() 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())
  • Related