⚡IntermediateFunctional Programming

Functional Interfaces

Use the built-in functional interfaces from java.util.function: Predicate, Function, Consumer, Supplier, and BiFunction.

Overview

Java 8 introduced java.util.function, a package of ready-made functional interfaces so you do not have to define your own for common patterns. The four most important ones are: Predicate<T> (test a condition), Function<T,R> (transform a value), Consumer<T> (act on a value), and Supplier<T> (produce a value). Bi-variants accept two inputs: BiPredicate, BiFunction, BiConsumer. Understanding these interfaces is essential for the Stream API and many library methods.

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.

Loading editor…

Completed: 0 / 4 exercises

Knowledge Check

Question 1 of 4

Which method do you call on a Predicate<T> to test a value?