Java's Four Core Functional Interfaces Explained

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 T to an output of type R.
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 of Function): T apply(T t) – same input/output type.
  • BinaryOperator<T> (subinterface of BiFunction): 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>: return int, long, double respectively.
  • IntFunction<R>, LongFunction<R>, DoubleFunction<R>: primitive input, object output.

Tags: java Functional Programming Lambda Expressions Consumer Supplier

Posted on Sun, 10 May 2026 22:01:03 +0000 by jsnyder2k