How to apply multiple Filters on Java Stream?

萝らか妹 提交于 2019-12-05 00:54:55

I suppose you want to keep all the TestObjects that satisfy all the conditions specified by the map?

This will do the job:

List<TestObject> newList = list.stream()
        .filter(x ->
                filterMap.entrySet().stream()
                        .allMatch(y ->
                                x.getProperty(y.getKey()) == y.getValue()
                        )
        )
        .collect(Collectors.toList());

Translated into "English",

filter the list list by keeping all the elements x that:

  • all of the key value pairs y of filterMap must satisfy:
    • x.getProperty(y.getKey()) == y.getValue()

(I don't think I did a good job at making this human readable...) If you want a more readable solution, I recommend Jeroen Steenbeeke's answer.

To apply a variable number of filter steps to a stream (that only become known at runtime), you could use a loop to add filter steps.

Stream<TestObject> stream = list.stream();
for (Predicate<TestObject> predicate: allPredicates) {
  stream = stream.filter(predicate);
}
list = stream.collect(Collectors.toList());

A more general approach is to create a multi filter (Predicate) which is concatenated using Predicate.and(...) or Predicate.or(...). This is applicable to anything using Predicate - first of all Stream and Optional.
Since the result is a Predicate itself one can continue with Predicate.and(...) or Predicate.or(...) or with building more complex predicates using MultiPredicate again.

class MultiPredicate {

    public static <T> Predicate<T> matchingAll(Collection<Predicate<T>> predicates) {
        Predicate<T> multiPredicate = to -> true;
        for (Predicate<T> predicate : predicates) {
            multiPredicate = multiPredicate.and(predicate);
        }

        return multiPredicate;    
    }

    @SafeVarargs
    public static <T> Predicate<T> matchingAll(Predicate<T> first, Predicate<T>... other) {
        if (other == null || other.length == 0) {
            return first;
        }

        Predicate<T> multiPredicate = first;
        for (Predicate<T> predicate : other) {
            multiPredicate = multiPredicate.and(predicate);
        }

        return multiPredicate;
    }

    public static <T> Predicate<T> matchingAny(Collection<Predicate<T>> predicates) {
        Predicate<T> multiPredicate = to -> false;
        for (Predicate<T> predicate : predicates) {
            multiPredicate = multiPredicate.or(predicate);
        }

        return multiPredicate;    
    }

    @SafeVarargs
    public static <T> Predicate<T> matchingAny(Predicate<T> first, Predicate<T>... other) {
        if (other == null || other.length == 0) {
            return first;
        }

        Predicate<T> multiPredicate = first;
        for (Predicate<T> predicate : other) {
            multiPredicate = multiPredicate.or(predicate);
        }

        return multiPredicate;
    }

}

Applying to the question:

public static void main(String... args) {
    List<TestObject> list = new ArrayList<>();
    Map<Integer, Integer> filterMap = new HashMap<>();
    list.add(new TestObject(1, 2, 3));
    list.add(new TestObject(1, 2, 4));
    list.add(new TestObject(1, 4, 3));
    filterMap.put(3, 3); // Filter property3 == 3
    filterMap.put(2, 2); // Filter property2 == 2

    List<Predicate<TestObject>> filters = filterMap.entrySet().stream()
            .map(filterMapEntry -> mapToFilter(filterMapEntry))
            .collect(Collectors.toList());

    Predicate<TestObject> multiFilter = MultiPredicate.matchingAll(filters);
    List<TestObject> filtered = list.stream()
            .filter(multiFilter)
            .collect(Collectors.toList());
    for (TestObject to : filtered) {
        System.out.println("(" + to.getProperty(1) + "|" + to.getProperty(2) + "|" + to.getProperty(3) + ")");
    }

}

private static Predicate<TestObject> mapToFilter(Entry<Integer,Integer> filterMapEntry) {
    return to -> to.getProperty(filterMapEntry.getKey()) ==  filterMapEntry.getValue();
}

In this case all filters have to match. The result is:

(1|2|3)  

If we use MultiPredicate.matchingAny(...) the result is:

(1|2|3)  
(1|2|4)  
(1|4|3)  

It has nothing to do with filter. Actually the filter never work as per your code. Look at

//Does not apply the result
filterMap.forEach((key, value) -> list.stream()
        .filter(testObject -> testObject.getProperty(key) == value)
        .collect(Collectors.toList())
);

List has been filtered but nothing is changed here. No element has been deleted and No object address has been changed either. Try removeIf

// Does not apply the result
filterMap.forEach((key, value) -> list.removeIf(testObject -> testObject.getProperty(key) != value));

output is

1 2 3
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!