Java Functional Interfaces: A Comprehensive Guide with Examples
Learn about Java functional interfaces, introduced in Java 8. Discover how they simplify code and enable functional programming. This guide includes examples and explanations of key concepts, such as lambda expressions and method references.
Java - Functional Interfaces
Functional interfaces were introduced in Java 8 along with lambda expressions and method references. These features significantly improved functional programming in Java, making the code cleaner and more readable. Before Java 8, developers had to write a lot of boilerplate code to perform basic tasks, such as creating a class and its instances to call a function. Lambda expressions simplify this process by allowing you to directly implement functional interfaces without needing concrete or anonymous class objects.
What is a Functional Interface?
A functional interface contains exactly one abstract method but can include any number of default or static methods. For example, the Comparable
interface with its single compareTo()
method is used for comparisons. Java 8 introduced many functional interfaces, making lambda expressions more efficient to use.
@FunctionalInterface Annotation
By definition, any interface with a single abstract method is considered a functional interface. The @FunctionalInterface
annotation is optional but helps the compiler enforce the rule that only one abstract method is present, increasing readability and maintainability of the code.
Types of Functional Interfaces in Java
There are four primary types of functional interfaces in Java:
1. Predicate Functional Interface
A predicate functional interface has a method that accepts one argument and returns either true
or false
. It's commonly used for comparisons and filtering elements. Java provides predicate interfaces for primitive types like IntPredicate
, DoublePredicate
, and LongPredicate
.
Example: Filtering Even Numbers
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class Tester {
public static void main(String args[]) {
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
Predicate isEvenNumber = n -> n % 2 == 0;
numbers = numbers.stream().filter(isEvenNumber).toList();
System.out.println(numbers);
}
}
Output
[2, 4, 6, 8]
2. Consumer Functional Interface
A consumer functional interface accepts one argument and does not return anything. It is mainly used for operations that produce side effects, like printing or modifying elements. Java also provides primitive-specific consumers such as IntConsumer
, DoubleConsumer
, and LongConsumer
.
Example: Printing Elements
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class Tester {
public static void main(String args[]) {
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
Consumer consumer = (value) -> System.out.println(value);
Consumer consumer1 = System.out::println;
System.out.println("Using lambda expression:");
numbers.forEach(consumer);
System.out.println("Using method reference:");
numbers.forEach(consumer1);
}
}
Output
Using lambda expression:
1
2
3
4
5
6
7
8
Using method reference:
1
2
3
4
5
6
7
8
3. Supplier Functional Interface
A supplier functional interface does not accept any arguments but returns a value. It's typically used to generate values lazily, such as fetching random numbers or generating a sequence.
Example: Generating Random Numbers
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class Tester {
public static void main(String args[]) {
Supplier supplier = () -> (int)(Math.random() * 10);
List randomNumbers = new ArrayList<>();
for (int i = 0; i < 10; i++) {
randomNumbers.add(supplier.get());
}
System.out.println(randomNumbers);
}
}
Output
[0, 8, 8, 8, 8, 5, 7, 5, 5, 9]
4. Function Functional Interface
The function functional interface accepts one argument and returns a result. It's useful for transforming data, such as calculating the square of numbers. Java also provides specific versions for primitive types like IntFunction
, DoubleFunction
, and LongFunction
.
Example: Squaring Numbers
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
public class Tester {
public static void main(String args[]) {
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
Function squared = (value) -> value * value;
List squaredNumbers = numbers.stream().map(squared).toList();
System.out.println(squaredNumbers);
}
}
Output
[1, 4, 9, 16, 25, 36, 49, 64]
Existing Functional Interfaces Prior to Java 8
Several interfaces already existed before Java 8 that now qualify as functional interfaces. For example:
Runnable
− Provides therun()
method.Callable
− Provides thecall()
method.ActionListener
− Provides theactionPerformed()
method.Comparable
− Provides thecompareTo()
method for comparing two elements.
Example: Runnable Interface
Example: Runnable Interface with Lambda
public class Tester {
public static void main(String args[]) {
// Anonymous inner class
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread 1 is running");
}
}).start();
// Lambda expression
new Thread(() -> System.out.println("Thread 2 is running")).start();
}
}
Output
Thread 1 is running
Thread 2 is running