Functional Interfaces in Java | Generated by AI
The java.util.function package in Java provides a collection of functional interfaces that are designed to support functional programming, introduced in Java 8. These interfaces each have a single abstract method, making them compatible with lambda expressions and method references. This response explains how to use some of the most common functional interfaces in this package—Function<T, R>, Predicate<T>, Consumer<T>, and Supplier<T>—with practical examples.
What Are Functional Interfaces?
A functional interface is an interface with exactly one abstract method. The java.util.function package offers predefined functional interfaces for common tasks, so you don’t need to create your own. These interfaces are widely used with lambda expressions, method references, and the Stream API to write concise and expressive code.
Here’s how to use the key interfaces:
1. Function<T, R>: Transforming Input to Output
The Function<T, R> interface represents a function that takes an input of type T and produces an output of type R. Its abstract method is apply.
Example: Get the Length of a String
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
Function<String, Integer> stringLength = s -> s.length();
System.out.println(stringLength.apply("Hello")); // Outputs: 5
}
}
- Explanation: The lambda expression
s -> s.length()defines aFunctionthat takes aString(T) and returns anInteger(R). Theapplymethod executes this logic.
2. Predicate<T>: Testing a Condition
The Predicate<T> interface represents a boolean-valued function that takes an input of type T. Its abstract method is test.
Example: Check if a Number is Even
import java.util.function.Predicate;
public class Main {
public static void main(String[] args) {
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // Outputs: true
System.out.println(isEven.test(5)); // Outputs: false
}
}
- Explanation: The lambda
n -> n % 2 == 0defines aPredicatethat returnstrueif the input is even. Thetestmethod evaluates this condition.
3. Consumer<T>: Performing an Action
The Consumer<T> interface represents an operation that takes an input of type T and returns no result. Its abstract method is accept.
Example: Print a String
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
Consumer<String> printer = s -> System.out.println(s);
printer.accept("Hello, World!"); // Outputs: Hello, World!
}
}
- Explanation: The lambda
s -> System.out.println(s)defines aConsumerthat prints its input. Theacceptmethod performs the action.
4. Supplier<T>: Generating a Result
The Supplier<T> interface represents a supplier of results, taking no input and returning a value of type T. Its abstract method is get.
Example: Generate a Random Number
import java.util.function.Supplier;
import java.util.Random;
public class Main {
public static void main(String[] args) {
Supplier<Integer> randomInt = () -> new Random().nextInt(100);
System.out.println(randomInt.get()); // Outputs a random integer between 0 and 99
}
}
- Explanation: The lambda
() -> new Random().nextInt(100)defines aSupplierthat generates a random integer. Thegetmethod retrieves the value.
Using Functional Interfaces with Streams
These interfaces shine in the Java Stream API, where they enable concise data processing. Here’s an example that filters, transforms, and prints a list of strings:
Example: Process a List of Strings
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
public class Main {
public static void main(String[] args) {
List<String> strings = Arrays.asList("a", "bb", "ccc", "dddd");
Predicate<String> longerThanTwo = s -> s.length() > 2; // Filter strings longer than 2
Function<String, String> toUpperCase = s -> s.toUpperCase(); // Convert to uppercase
Consumer<String> printer = s -> System.out.println(s); // Print each result
strings.stream()
.filter(longerThanTwo) // Keeps "ccc" and "dddd"
.map(toUpperCase) // Converts to "CCC" and "DDDD"
.forEach(printer); // Outputs: CCC, DDDD (on separate lines)
}
}
- Explanation:
filteruses aPredicateto keep strings with length > 2.mapuses aFunctionto transform strings to uppercase.forEachuses aConsumerto print each result.
Using Method References
You can make this even shorter with method references:
strings.stream()
.filter(s -> s.length() > 2)
.map(String::toUpperCase) // Method reference for Function
.forEach(System.out::println); // Method reference for Consumer
Composing Functional Interfaces
Some interfaces allow composition for more complex operations:
- Function Composition: Use
andThenorcompose.Function<String, Integer> toLength = s -> s.length(); Function<Integer, String> toString = i -> "Length is " + i; Function<String, String> combined = toLength.andThen(toString); System.out.println(combined.apply("Hello")); // Outputs: Length is 5 - Predicate Combination: Use
and,or, ornegate.Predicate<String> isLong = s -> s.length() > 5; Predicate<String> startsWithA = s -> s.startsWith("A"); Predicate<String> isLongAndStartsWithA = isLong.and(startsWithA); System.out.println(isLongAndStartsWithA.test("Avocado")); // Outputs: true
Summary
Here’s when and how to use these interfaces:
Function<T, R>: Transform an input into an output (e.g.,apply).Predicate<T>: Test a condition (e.g.,test).Consumer<T>: Perform an action on an input (e.g.,accept).Supplier<T>: Generate a value without input (e.g.,get).- With Streams: Combine them for powerful data processing.
- Composition: Chain or combine them for complex logic.
You can implement these interfaces using lambda expressions (e.g., s -> s.length()) or method references (e.g., String::toUpperCase). They enable a functional programming style in Java, making your code more concise, readable, and reusable, especially with the Stream API.