This content originally appeared on Level Up Coding - Medium and was authored by Kanchana Ranmuthu
If you are a Java developer you may have used forEach(), filter () like Stream API offered stuff at least once in your life. But have you ever stopped to wonder how these methods work behind the scenes? What kind of magic powers their operation?
The answer lies in the Consumer, Predicate, and Supplier functional interfaces.
What Are Functional Interfaces?
Functional interface is a special type of interfaces in Java that only allow one abstract method but can have any number of default and static methods.
If you need more information about functional interfaces, please check the story linked at the end of this article.
Let's explore them in depth and see what consumer, supplier, and predicate interfaces are and what they do.
Consumer Interface
Consumer interface can be used in scenarios where it is provided a set of inputs and performs some operations but returns nothing.
It has two methods.
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after);
accept() is the abstract method. It contains a default method also as andThen(). accept() method accepts a generic argument type and its return type is void.
Predicate Interface
This interface is used for conditional checks.
It has an abstract method test() that takes a generic argument and returns a boolean.
boolean test(T t);
It also has a few other default and static methods like, and(), or(), isEqual(), etc.
Supplier Interface
This interface is used when producing an output without any input. It has an abstract method get() which returns generic type T.
T get();
Now it’s time to understand these interfaces more with examples.
Consumer Interface Example
Assume we have a class ConsumerDemo implemented from Consumer functional interface.
accept() method is overridden with a simple print statement. Then we can call that method inside the main method.
public class ConsumerDemo implements Consumer<Integer> {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
public static void main(String[] args) {
ConsumerDemo demo= new ConsumerDemo();
demo.accept(2);
}
}
The same implementation can be simplified using a lambda expression.
public class ConsumerDemo {
public static void main(String[] args) {
Consumer<Integer> consumer = (integer) -> System.out.println(integer);
}
}
Here, we do not need to implement the class from the interface and override the abstract method.
If this sounds a bit unfamiliar to you, the article linked at the end of this is for you:)
Now let’s switch the context for a moment and consider the forEach() method.
According to the documentation of the forEach() method, it returns nothing and accepts Consumer as its generic argument. This allows us to write code like the example below.
public class ConsumerDemo {
public static void main(String[] args) {
Consumer<Integer> consumer = (integer) -> System.out.println(integer);
List<Integer> list = List.of(1,2,3,4);
list.forEach(consumer);
}
}
But isn’t the Consumer itself essentially a lambda expression? Exactly! You can directly plug the lambda expression into forEach().
public class ConsumerDemo {
public static void main(String[] args) {
// Consumer<Integer> consumer = (integer) -> System.out.println(integer);
List<Integer> list = List.of(1,2,3,4);
list.forEach((integer) -> System.out.println(integer));
}
}
Congratulations! You’ve landed on the familiar forEach() method.
The reason we can use it seamlessly with a lambda expression is that the Consumer functional interface is working behind the scenes."
Predicate Interface Example
Let’s follow the same pattern as the consumer example.
PredicateDemo class implemented from the interface.
class PredicateDemo implements Predicate<Integer> {
@Override
public boolean test(Integer integer) {
return integer % 2 == 0;
}
public static void main(String[] args) {
PredicateDemo demo = new PredicateDemo();
System.out.println(demo.test(5));
}
}
This will check if the given number is even or not.
Then, the same thing with a Lambda expression.
class PredicateDemo {
public static void main(String[] args) {
Predicate<Integer> predicate = (integer) -> integer % 2 == 0;
System.out.println(predicate.test(5));
}
}
Let’s check the Java filter method for a moment.
It takes Predicate reference as the argument. That means we can use it as below.
class PredicateDemo {
public static void main(String[] args) {
Predicate<Integer> predicate = (integer) -> integer % 2 == 0;
List<Integer> list = List.of(1,2,3,4);
list.stream()
.filter(predicate)
.forEach(number -> System.out.println(number));
}
}
Our predicate function checks if the given number is even or not. So, it will filter the even numbers in the list and forEach() print them.
We can also plug the lambda expression of the predicate reference directly into the filter, as demonstrated here.
class PredicateDemo {
public static void main(String[] args) {
// Predicate<Integer> predicate = (integer) -> integer % 2 == 0;
List<Integer> list = List.of(1,2,3,4);
list.stream()
.filter((integer) -> integer % 2 == 0)
.forEach(number -> System.out.println(number));
}
}
You have just derived the filter() method you are familiar with!
Supplier Interface Example
Let’s follow the same example pattern here.
First, the SupplierDemo class implemented from the Supplier interface.
class SupplierDemo implements Supplier<Integer> {
@Override
public Integer get() {
return 0;
}
public static void main(String[] args) {
SupplierDemo demo = new SupplierDemo();
System.out.println(demo.get());
}
}
Then, the lambda expression for the above implementation.
class SupplierDemo {
public static void main(String[] args) {
Supplier<Integer> supplier = () -> 0;
System.out.println(supplier.get());
}
}
Let’s take a look at the documentation for the orElseGet() method.
As we can see, the argument is a type of Supplier reference, that allows us to do the following by putting supplier reference can be put into the orElseGet().
class SupplierDemo {
public static void main(String[] args) {
Supplier<Integer> supplier = () -> 0;
List<Integer> list = List.of();
int value = list.stream().findAny().orElseGet(supplier);
System.out.println(value);
}
}
If the list is not empty, the findAny() method will return a value from the list. However, if the list is empty, the orElseGet() method will be executed to provide a fallback value.
Just like the previous examples, we can replace the Supplier reference with a lambda expression which gives you a more familiar and convenient syntax.
class SupplierDemo {
public static void main(String[] args) {
//Supplier<Integer> supplier = () -> 0;
List<Integer> list = List.of();
int value = list.stream().findAny().orElseGet(() -> 0);
System.out.println(value);
}
}
That’s it! Hopefully, you now have a crystal-clear understanding of how lambdas integrate with Stream methods like filter(), forEach(), and others.
Many more methods utilize the Consumer, Predicate, and Supplier interfaces and they all functions in a similar fashion.
Conclusion
Java Stream API is a powerful toolkit that came with Java 8 and those features are widely used today. It introduced a functional way for writing codes to the Java world and many more developers are familiar with these syntax as of now.
The purpose of this story was to uncover the magic behind these functions, showing how they work behind the scenes and what makes them so powerful.
While it’s always a good practice to embrace the latest features of any programming language, understanding the fundamentals remains just as important.
If you need more information about the lambda functions and functional interfaces, please check my below article.
That’s it for today.
Happy Coding and see you next time!
But what are Consumer, Supplier, and Predicate Interfaces in Java? was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Kanchana Ranmuthu
Kanchana Ranmuthu | Sciencx (2025-01-06T01:43:43+00:00) But what are Consumer, Supplier, and Predicate Interfaces in Java?. Retrieved from https://www.scien.cx/2025/01/06/but-what-are-consumer-supplier-and-predicate-interfaces-in-java/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.