How to apply multiple Filters on Java Stream?

后端 未结 4 727
梦毁少年i
梦毁少年i 2021-01-11 09:27

I have to filter a Collection of Objects by a Map, which holds key value pairs of the Objects field names and field values. I am trying to apply all filters by stream().filt

相关标签:
4条回答
  • 2021-01-11 10:04

    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
    
    0 讨论(0)
  • 2021-01-11 10:14

    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)  
    
    0 讨论(0)
  • 2021-01-11 10:16

    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.

    0 讨论(0)
  • 2021-01-11 10:18

    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());
    
    0 讨论(0)
提交回复
热议问题