Functional interfaces

Core Java · lesson 36 of 42 · 4 min read

Match a lambda to the right java.util.function type instead of inventing your own.

Open this lesson in the learning hub

Key points

  • A functional interface has exactly one abstract method, which is what lets a lambda stand in for it.
  • Predicate answers a question, Function transforms, Supplier produces, Consumer accepts.
  • UnaryOperator<T> is a Function with the same input and output type; BiFunction takes two arguments.
  • They compose: andThen, negate, and and or build a bigger one from small pieces.
  • In hot code prefer IntPredicate to Predicate<Integer> — the generic form boxes every value.
  • @FunctionalInterface is optional, but it turns "someone added a second method" into a compile error.

Example

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

public class Main {

    @FunctionalInterface
    interface Discount {                       // your own: exactly one abstract method
        double apply(double amount);
    }

    static double checkout(double amount, Discount d) { return d.apply(amount); }

    public static void main(String[] args) {
        Predicate<String> blank = s -> s.isBlank();
        Function<String, Integer> length = String::length;
        Supplier<List<String>> maker = ArrayList::new;
        Consumer<String> printer = s -> System.out.println("  Consumer got: " + s);
        BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
        UnaryOperator<String> shout = s -> s.toUpperCase() + "!";

        System.out.println("Predicate  : blank on two spaces = " + blank.test("  "));
        System.out.println("Function   : length of java      = " + length.apply("java"));
        System.out.println("Supplier   : maker.get()         = " + maker.get());
        printer.accept("a value");
        System.out.println("BiFunction : add(2, 3)           = " + add.apply(2, 3));
        System.out.println("UnaryOp    : shout(hi)           = " + shout.apply("hi"));

        System.out.println("andThen    : " + length.andThen(n -> n * 2).apply("java"));
        System.out.println("negate     : " + blank.negate().test("  "));
        System.out.println("and        : " + blank.negate().and(s -> s.length() > 2).test("java"));

        System.out.println("your own interface: " + checkout(100, amount -> amount * 0.9));
        System.out.println("a lambda only fits an interface with exactly one abstract method");
    }
}

Learn the six shapes and you almost never declare an interface yourself.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Core Java course, and every lesson in it is listed on the Core Java contents page.