Functional Interfaces
Use the built-in functional interfaces from java.util.function: Predicate, Function, Consumer, Supplier, and BiFunction.
Overview
Key Points
- •Predicate<T>: boolean test(T t) - checks a condition
- •Function<T,R>: R apply(T t) - transforms T into R
- •Consumer<T>: void accept(T t) - acts on T, returns nothing
- •Supplier<T>: T get() - produces T, takes no input
- •UnaryOperator<T>: extends Function<T,T> - same input and output type
- •BinaryOperator<T>: extends BiFunction<T,T,T> - two T inputs, one T output
- •Predicate.and(), or(), negate() for composing predicates
- •Function.andThen(), compose() for chaining functions
Examples
Predicate: test a condition
import java.util.function.Predicate;
import java.util.List;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<Integer> isPositive = n -> n > 0;
System.out.println(isEven.test(4)); // true
System.out.println(isEven.test(7)); // false
// Compose predicates
Predicate<Integer> isEvenAndPositive = isEven.and(isPositive);
Predicate<Integer> isEvenOrPositive = isEven.or(isPositive);
Predicate<Integer> isOdd = isEven.negate();
System.out.println(isEvenAndPositive.test(4)); // true
System.out.println(isEvenAndPositive.test(-4)); // false
// Filter a list with a predicate
List<Integer> nums = List.of(-3, -2, 0, 1, 2, 3, 4, 5);
List<Integer> filtered = new ArrayList<>();
nums.forEach(n -> { if (isEvenAndPositive.test(n)) filtered.add(n); });
System.out.println("Even and positive: " + filtered); // [2, 4]
}
}and, or, negate build new Predicates from existing ones. This is function composition without defining new lambda expressions.
Function: transform values
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
Function<String, Integer> length = String::length; // method reference
Function<Integer, String> stars = n -> "*".repeat(n);
System.out.println(length.apply("hello")); // 5
// andThen: apply length, then stars
Function<String, String> lengthToStars = length.andThen(stars);
System.out.println(lengthToStars.apply("Java")); // ****
// compose: stars first, then length
Function<Integer, Integer> starsLength = stars.compose(length).andThen(String::length);
// equivalent: length of "*".repeat(length of input)
System.out.println(starsLength.apply(3)); // same as starsLength(3) = 3
// Function<T,R> chain
Function<String, String> trim = String::trim;
Function<String, String> upper = String::toUpperCase;
Function<String, String> process = trim.andThen(upper);
System.out.println(process.apply(" hello world ")); // HELLO WORLD
}
}andThen(g) means apply this first, then g. compose(g) means apply g first, then this.
Consumer and Supplier
import java.util.function.Consumer;
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
// Consumer: receives a value, returns nothing
Consumer<String> print = System.out::println;
Consumer<String> printUpper = s -> System.out.println(s.toUpperCase());
print.accept("hello"); // hello
printUpper.accept("hello"); // HELLO
// andThen: chain consumers
Consumer<String> printBoth = print.andThen(printUpper);
printBoth.accept("world"); // world
WORLD
// Supplier: takes nothing, returns a value
Supplier<String> greeting = () -> "Hello, World!";
Supplier<Double> random = Math::random;
System.out.println(greeting.get()); // Hello, World!
System.out.printf("Random: %.4f%n", random.get());
}
}Consumer.andThen chains two consumers: both run in sequence on the same input. Supplier is useful for lazy evaluation: the value is only computed when get() is called.
Common Mistakes
- Calling apply() on a Predicate (should be test()) or test() on a Function (should be apply()). Each interface has its own abstract method name
- Using Function<T,T> when UnaryOperator<T> is available. UnaryOperator is cleaner and more specific
- Forgetting that Consumer.andThen returns a new Consumer but does not run either consumer yet
Note
Prefer the built-in interfaces over defining your own. If you need a function from T to boolean, use Predicate<T> rather than defining a custom CheckFunction<T>. This keeps the code consistent and interoperable with libraries.
Exercises
Write Java code and click Run to execute it instantly. When your output looks correct, mark as done.
Completed: 0 / 4 exercises
Knowledge Check
Question 1 of 4