-2

We are doing some chained filters to evaluate a bunch of expressions to get only the items that match all the expressions. It looks something like this:

getItems().stream()
          .filter(Item::isActive)
          .filter(item -> fulfillsConditionA(item))
          .filter(item -> item.getSomeNumber() < anyOtherNumber)
          .forEach(item -> doSomeStuff(item));

While this works as expected, there is no way to see/log what specific filter predicate filtered out an item.

Is there a way to get the unmatched elements of a filter() call, or is there a way to add a Consumer or Function as a second parameter to the filter() call which is executed when the filter does not match?

Gautham M
  • 4,816
  • 3
  • 15
  • 37
Benny
  • 109
  • 1
  • 6
  • 2
    Does this answer your question? [How to partition a list by predicate using java8?](https://stackoverflow.com/questions/36678571/how-to-partition-a-list-by-predicate-using-java8) – Tom Feb 23 '22 at 13:49
  • Thanks for the link. I was unaware of the `partitionBy` method, it definitely helps. However, it seems like I have to start a new lambda chain after every `partitionBy` call. Do you see a way how I could do this in a single lambda chain? – Benny Feb 23 '22 at 14:05
  • @Benny `how I could do this in a single lambda chain?` - You can combine all the conditions into a **single predicate**, no need for *chaining* if you don't need to do something in between these filters. If the predicate applies to be lengthy and difficult to comprehend, then create a method with a self-explanatory name and put this logic into it. And in that case you might replace the lambda with a **method reference**. – Alexander Ivanchenko Feb 23 '22 at 17:46

2 Answers2

0

You need to map each condition to some constant. Like -1 for first filter, -2 for second, etc. 0 may be used to denote that the item is not filtered out. Then use groupingBy to collect to a map.

Function<Item, Integer> fn = item -> {
    if(!item.isActive()) {
        return -1;
    } else if(/*condition 2*/) {
        return -2;
    }
    /*.....*/
    else{
        // item that should be used to invoke doSomeStuff
        return 0; 
    }

};

Map<Integer, Item> map = getItems().stream()
                                   .collect(Collectors.groupingBy(fn));

map.entrySet()
   .stream()
   .filter(entry -> entry.getKey() == 0)
   .forEach(entry -> doSomeStuff(entry.getValue());
          
Gautham M
  • 4,816
  • 3
  • 15
  • 37
0

What you are asking is to collect the reverse set of your filters.

Just have to just negate your filter's requirement and collect, just you will have to do it in multiple steps, for example from your code.

getItems().stream()
          .filter(item -> !fulfillsConditionA(item))
          .collect(Collectors.toList());
Dimitris
  • 560
  • 3
  • 17