3 minutes
Understanding Functional Interfaces in Java
A functional interface in Java is an interface that contains exactly one abstract method. Introduced in Java 8, they are the backbone of lambda expressions and the Stream API, enabling a more functional style of programming. They are also known as Single Abstract Method (SAM) interfaces.
To ensure an interface is a functional interface, you can use the @FunctionalInterface annotation. The compiler will then trigger an error if the interface does not meet the requirements.
Here’s a simple example:
@FunctionalInterface
interface MyFunctionalInterface {
void execute(String message); // The single abstract method
}
public class FunctionalInterfaceExample {
public static void main(String[] args) {
// Using a lambda expression to implement the interface
MyFunctionalInterface myLambda = (msg) -> System.out.println("Executing: " + msg);
myLambda.execute("Hello, Functional World!");
}
}
Core Functional Interfaces in java.util.function
Java 8 introduced a set of standard functional interfaces in the java.util.function package. These are widely used and cover most common use cases. Let’s explore the four main ones.
1. Consumer<T>
A Consumer “consumes” an input value and performs an operation on it without returning any result.
- Abstract Method:
void accept(T t) - Use Case: Ideal for operations with side effects, such as printing to the console, logging, or modifying an object.
Example:
import java.util.function.Consumer;
import java.util.Arrays;
import java.util.List;
public class ConsumerExample {
public static void main(String[] args) {
Consumer<String> printer = s -> System.out.println(s);
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(printer); // Prints each name
}
}
2. Predicate<T>
A Predicate evaluates a condition on an input value and returns a boolean result.
- Abstract Method:
boolean test(T t) - Use Case: Perfect for filtering data, validation, and conditional checks. It’s heavily used in the Stream API’s
filter()method.
Example:
import java.util.function.Predicate;
import java.util.stream.Stream;
public class PredicateExample {
public static void main(String[] args) {
Predicate<String> isLongerThan5 = s -> s.length() > 5;
Stream.of("Java", "Python", "JavaScript")
.filter(isLongerThan5)
.forEach(System.out::println); // Prints "JavaScript"
}
}
3. Function<T, R>
A Function takes an input of type T and produces a result of type R. It’s used for transformation and mapping.
- Abstract Method:
R apply(T t) - Use Case: Transforming one object into another, such as converting a
Stringto its length or mapping a DTO to an entity.
Example:
import java.util.function.Function;
import java.util.stream.Stream;
public class FunctionExample {
public static void main(String[] args) {
Function<String, Integer> stringLength = s -> s.length();
Stream.of("Apple", "Banana", "Cherry")
.map(stringLength)
.forEach(System.out::println); // Prints 5, 6, 6
}
}
4. Supplier<T>
A Supplier “supplies” a value of type T without taking any input.
- Abstract Method:
T get() - Use Case: Useful for lazy generation of values, such as creating new objects, generating random numbers, or providing default values.
Example:
import java.util.function.Supplier;
import java.time.LocalDateTime;
public class SupplierExample {
public static void main(String[] args) {
Supplier<LocalDateTime> currentTimeSupplier = () -> LocalDateTime.now();
System.out.println(currentTimeSupplier.get()); // Prints the current date and time
}
}
By understanding and using these core functional interfaces, you can write more expressive, concise, and powerful Java code.