Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to apply multiple Filters on Java Stream?

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().filter().

The Objects are actually JSON, therefore the Map holds the names of its variables as well as the value they have to contain in order to be accepted, but for simplicity reasons and because its not relevant to the question I wrote a simple Testclass for simulating the behaviour:

public class TestObject {

  private int property1;
  private int property2;
  private int property3;

  public TestObject(int property1, int property2, int property3) {
      this.property1 = property1;
      this.property2 = property2;
      this.property3 = property3;
  }

  public int getProperty(int key) {
      switch(key) {
          case 1: return property1;
          case 2: return property2;
          default: return property3;
      }
  }
}

What I have tried so far:

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

    //Does not apply the result
    filterMap.forEach((key, value) -> list.stream()
            .filter(testObject -> testObject.getProperty(key) == value)
            .collect(Collectors.toList())
    );
    /* Gives error: boolean can not be converted to void
    list = list.stream()
            .filter(testObject -> filterMap.forEach((key, value) -> testObject.getProperty(key) == value))
            .collect(Collectors.toList()
            );
    */
    //Printing result

    list.forEach(obj -> System.out.println(obj.getProperty(1) + " " + obj.getProperty(2) + " " + obj.getProperty(3)));
}

I tried putting forEach of the Map first and the stream of the Collection first, but both solutions did not work as intended. The desired output of this example would be only to print the object with the values property1=1, property2=2 and property3=3.

How can I apply all filters correctly like when you would put them one after another in the code with a fixed amount of filters?

With a known amount of filters:

list.stream().filter(...).filter(...)

Edit:

Sweeper summed my question up very well in his answer, so just for clarification (and probably future readers) here again: I want to keep all Objects that satisfy all filters.

like image 676
Absent Avatar asked Jul 05 '18 08:07

Absent


People also ask

Can we have multiple filter in stream Java?

3.1. You can chain filter method on the stream to evaluate each element against multiple conditions. For example, the below code contains two filters: First filter method evaluates whether the fruit name ends with “fruit” suffix and returns the results “jack fruit” and “dragon fruit”

Can we use 2 filters in stream?

Combining two filter instances creates more objects and hence more delegating code but this can change if you use method references rather than lambda expressions, e.g. replace filter(x -> x. isCool()) by filter(ItemType::isCool) .

How do you add two conditions to a stream filter?

Use negate() to write the reverse/negative conditions so that a single predicate may serve true and false – both scenarios. Use and() to combine two predicates for a logical AND operation. Use or() to combine two predicates for a logical OR operation.


2 Answers

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.

like image 135
Sweeper Avatar answered Oct 21 '22 11:10

Sweeper


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());
like image 33
Jeroen Steenbeeke Avatar answered Oct 21 '22 12:10

Jeroen Steenbeeke