How Java filter() Method Works in Background?
filter() method is used in functional programming as an intermediate method. A predicate is a non-interfering, stateless predicate to apply to each element to determine if it should be included.
Syntax: filter() method
Parameter: A predicate
Return Type: A stream consisting of the elements of the passed stream that match the given predicate.
Have you ever wondered what is happening when we write the below sample that is a lambda expression with an element mapping to an ‘element%2==0‘? So what actually is happening behind the screens, how is it actually working? All the magic of this is based on something which is called a Functional Interface as the functional interface has exactly one abstract method. Since default methods have an implementation, they are not abstract. For example Predicate Interface, Consumer Interface are functional interfaces.
List.of(10,5,23,54).stream().filter(element -> element%2==0);
Implementation: Consider the program below which uses the filter() method to eliminate the odd numbers and returns the stream of numbers that are divisible by 2 that is even numbers.
12 34 32 4
- When the stream of [12, 34, 67, 19, 32, 4] is passed to the filter method, it tests each element using the provided predicate (element → element%2==0) and returns the stream of even numbers [12, 34, 32, 4].
- Now, what is happening behind the screens? How does the code fragment ‘element → element%2 == 0 ‘ get sent out to the filter method.
- If we look at the signature of the filter() method is as follows where one can see that it is taking a Predicate in the argument.
public abstract Stream<T> filter(Predicate<? super T> predicate)
- So when we go to the documentation of Predicate, it has the following signature is as follows with @FunctionalInterface annotation written above it
public interface Predicate<T>
- The functional method which is present in this Predicate interface is as follows:
boolean Test(T t) ;
- It returns a composed predicate that represents a logical AND of this predicate and another. When evaluating the composed predicate, if this predicate is false, then the other predicate is not evaluated.
- In the predicate interface, Test() is the only method that does not have any default implementation written. So now, how does ‘element -> element%2 == 0‘ gets mapped to the implementation of the Predicate interface.
- We are having a method test() which is accepting an interface Predicate as an argument, then we can only pass the objects of the implementations of that interface as parameters to that method.
12 34 32 4
Note: Both of the programs produces the same output.
When we pass the object of EvenNumberPredicate() to the filter method as an argument, what it does is for every element of the stream passed to it, it will check the condition provided in the overridden test() method of EvenNumberPredicate() and it includes the element to the returning stream if test() returns true, else it refuses to include the element to the returning stream if test() method returns false. For example: for element 12, test() method returns true, so it is included in the returning stream, while for the element 67 test() returns false, so 67 is not included in the returning stream. Example 2 shows exactly what is happening when we pass some logic to the filter() method. This means, when we pass some logic to the filter() method, the java compiler in the background creates an implementation of the functional interface.
Note: The most important feature of a functional interface just like Predicate is that it would have only one method which will not have a definition, and basically what we are doing when we are creating a Lambda Expression is that we are providing implementation (or logical code) for that specific method.
In this case (i.e. filter()) we are saying to the compiler that takes the only method which is not implemented test() in the case of Predicate and provides the logic written in the filter method as the implementation for it.