The java.util.function package in Java 8 contains four fundamental functional interfaces: Consumer, Function, Predicate, and Supplier. These provide a standard way to utilize lambda expressions and method references.
Functional Programing in Java
A functional interface in Java is an interface that contains exactly one abstract method. This constraint makes them suitable for lambda expression targets. A lambda expression essentially provides an implementation for that single abstract method, functioning as an instance of the functional interface.
While syntactically similar, lambdas are not simple syntactic sugar for anonymous inner classes; they differ in underlying implementation. The @FunctionalInterface annotation (optional) enforces the single-abstract-method rule at compile time, but any interface satisfying the definition is treated as functional by the compiler.
Lambda as a Paramter
Methods can accept functional interfaces as parameters, enabling lambdas as arguments. For example:
@FunctionalInterface
interface MyFunctionalInterface {
void execute();
}
public class TestFunctional {
public static void performAction(MyFunctionalInterface action) {
action.execute();
}
public static void main(String[] args) {
performAction(() -> System.out.println("Lambda executed!"));
}
}
Lambda as a Return Value
Methods can return functional interfaces, with lambdas as concrete implementations.
import java.util.Arrays;
import java.util.Comparator;
public class LambdaComparator {
private static Comparator<String> createComparator() {
return (a, b) -> b.length() - a.length();
}
public static void main(String[] args) {
String[] array = {"abc", "ab", "abcd"};
System.out.println(Arrays.toString(array));
Arrays.sort(array, createComparator());
System.out.println(Arrays.toString(array));
}
}
The Four Core Functional Interfaces
Java 8 provides these interfaces for common use cases, reducing the need to define custom ones.
1. Consumer<T>: Consuming Interface
- Method:
void accept(T t) - Takes an input, returns nothing (side-effect oriented).
import java.util.function.Consumer;
public class ConsumerExample {
public static void main(String[] args) {
processValue(100.0, x -> System.out.println("Value: " + x));
}
public static void processValue(Double value, Consumer<Double> consumer) {
consumer.accept(value);
}
}
2. Supplier<T>: Supplying Interface
- Method:
T get() - Takes no input, produces a result (factory pattern).
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
public class SupplierExample {
public static void main(String[] args) {
Random rand = new Random();
List<Integer> numbers = generateList(5, () -> rand.nextInt(10));
numbers.forEach(System.out::println);
}
public static List<Integer> generateList(int count, Supplier<Integer> supplier) {
List<Integer> result = new ArrayList<>();
for (int i = 0; i < count; i++) {
result.add(supplier.get());
}
return result;
}
}
3. Function<T, R>: Fnuctional Interface
- Method:
R apply(T t) - Transforms an input of type
Tto an output of typeR.
import java.util.function.Function;
public class FunctionExample {
public static void main(String[] args) {
String original = " hello ";
String trimmed = transformString(original, s -> s.trim());
String firstThree = transformString(original, s -> s.substring(0, 3));
System.out.println(trimmed);
System.out.println(firstThree);
}
public static String transformString(String input, Function<String, String> function) {
return function.apply(input);
}
}
4. Predicate<T>: Predicate Interface
- Method:
boolean test(T t) - Evaluates a condition on an input, returns a boolean.
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class PredicateExample {
public static void main(String[] args) {
List<Integer> values = new ArrayList<>();
values.add(102);
values.add(172);
values.add(13);
values.add(82);
values.add(109);
List<Integer> filtered = filterIntegers(values, x -> x > 100);
filtered.forEach(System.out::println);
}
public static List<Integer> filterIntegers(List<Integer> list, Predicate<Integer> predicate) {
List<Integer> result = new ArrayList<>();
for (Integer item : list) {
if (predicate.test(item)) {
result.add(item);
}
}
return result;
}
}
Additional Specialized Interfaces
BiFunction<T, U, R>:R apply(T t, U u)– two inputs, one output.UnaryOperator<T>(subinterface ofFunction):T apply(T t)– same input/output type.BinaryOperator<T>(subinterface ofBiFunction):T apply(T t1, T t2)– two same-type inputs.BiConsumer<T, U>:void accept(T t, U u)– two inputs, no output.ToIntFunction<T>,ToLongFunction<T>,ToDoubleFunction<T>: returnint,long,doublerespectively.IntFunction<R>,LongFunction<R>,DoubleFunction<R>: primitive input, object output.