Java functional interfaces are a powerful feature for enabling functional programming in Java. Here's an overview:
What are Functional Interfaces?
A functional interface in Java is an interface that contains exactly one abstract method. They can have multiple default or static methods but only one abstract method. Functional interfaces are the foundation of lambda expressions in Java.
Examples in the java.util.function
package:
Predicate: Accepts a single argument and returns a boolean (
test(T t)
).Function: Accepts one argument and produces a result (
apply(T t)
).Supplier: Produces a result without taking any input (
get()
).Consumer: Accepts a single input and performs some operation without returning a result (
accept(T t)
).BiFunction: Similar to
Function
but takes two arguments (apply(T t, U u)
).
How to Use Functional Interfaces?
Lambda Expressions:
import java.util.function.*;
public class FunctionalInterfaceDemo {
public static void main(String[] args) {
Predicate<Integer> isEven = x -> x % 2 == 0;
System.out.println(isEven.test(4)); // true
Function<String, Integer> stringLength = String::length;
System.out.println(stringLength.apply("Java")); // 4
Supplier<Double> randomNumber = Math::random;
System.out.println(randomNumber.get());
Consumer<String> print = System.out::println;
print.accept("Hello, Functional Interfaces!");
}
}
Java streams heavily utilize functional interfaces to provide a powerful API for processing collections and other data sources in a functional and declarative style. Here's an overview of how functional interfaces work in streams:
Commonly Used Functional Interfaces in Streams
Predicate<T>
Used for filtering elements in a stream.
Represents a condition to test (returns a boolean).
Method:
boolean test(T t)
Example:
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0) // Predicate<T>
.toList();
System.out.println(evenNumbers); // [2, 4, 6]
Function<T, R>
Used for mapping elements to a different type.
Method:
R apply(T t)
Example:
List<String> names = List.of("Alice", "Bob", "Charlie");
List<Integer> nameLengths = names.stream()
.map(String::length) // Function<T, R>
.toList();
System.out.println(nameLengths); // [5, 3, 7]
Consumer<T>
Used to perform an action on each element (e.g., printing or modifying elements).
Method:
void accept(T t)
Example:
List<String> names = List.of("Alice", "Bob", "Charlie");
names.stream()
.forEach(System.out::println); // Consumer<T>
Supplier<T>
Used to supply values, typically for initialization or lazy evaluation.
Streams don't directly use
Supplier<T>
often but can utilize it for creating elements dynamically.
Example:
Stream<Double> randomNumbers = Stream.generate(Math::random); // Supplier<T>
randomNumbers.limit(5).forEach(System.out::println);
BiFunction<T, U, R>
Used in situations requiring two input arguments, such as reducing operations with an accumulator.
Method:
R apply(T t, U u)
Example:
List<Integer> numbers = List.of(1, 2, 3, 4);
Integer sum = numbers.stream()
.reduce(0, Integer::sum); // BiFunction<T, U, R>
System.out.println(sum); // 10
Comparator<T>
Used for sorting operations.
Method:
int compare(T o1, T o2)
Example:
List<String> names = List.of("Alice", "Charlie", "Bob");
List<String> sorted = names.stream()
.sorted((a, b) -> a.compareToIgnoreCase(b)) // Comparator<T>
.toList();
System.out.println(sorted); // [Alice, Bob, Charlie]
Combining Functional Interfaces in Streams
A stream operation often combines multiple functional interfaces to perform complex transformations:
Example: Filter, Map, and Collect
List<String> names = List.of("Alice", "Bob", "Charlie", "David");
List<String> shortNames = names.stream()
.filter(name -> name.length() <= 4) // Predicate<T>
.map(String::toUpperCase) // Function<T, R>
.toList(); // Collect to List
System.out.println(shortNames); // [BOB]
Example: Sorting and Collecting
List<Integer> numbers = List.of(5, 3, 8, 1);
List<Integer> sortedNumbers = numbers.stream()
.sorted((a, b) -> a - b) // Comparator<T>
.toList();
System.out.println(sortedNumbers); // [1, 3, 5, 8]