Functional Interfaces

Basic Java
≈ 2 min read

An interface defines the shape of behavior

A lambda expression needs a type. In Java, that type is provided by a functional interface — an interface with exactly one abstract method. The parameters of that method define the lambda parameters, and its return type defines what the lambda must return.

@FunctionalInterface
interface TextRule {
    boolean test(String value);
}

TextRule longText = text -> text.length() >= 5;
System.out.println(longText.test("Java"));

The interface may also have default and static methods: they do not add new abstract requirements and do not stop the interface from being functional. The @FunctionalInterface annotation is not required, but it lets the compiler verify this intention.

The main interfaces from java.util.function

For common scenarios, there is no need to create your own interfaces. The java.util.function package contains ready-made options:

  • Predicate<T> — receives a value and returns boolean;
  • Function<T, R> — transforms a value of type T into R;
  • Consumer<T> — receives a value and returns nothing;
  • Supplier<T> — receives nothing and creates or provides a value;
  • UnaryOperator<T> and BinaryOperator<T> — special cases of functions where the input and result have the same type.
Predicate<String> isLong = text -> text.length() >= 5;
Function<String, Integer> length = String::length;
Consumer<String> print = System.out::println;
Supplier<String> message = () -> "Ready";

The names are good clues to the meaning. A Predicate answers a question, a Function transforms, a Consumer consumes, and a Supplier provides a value. These interfaces appear constantly in the Stream API and other parts of the standard library.

One interface — different implementations

A functional-interface variable can store a lambda, a method reference, or a regular object whose class implements that interface. For the calling code, there is no difference: it works with the interface method.

Predicate<String> startsWithJ = text -> text.startsWith("J");

if (startsWithJ.test("Java")) {
    System.out.println("match");
}
Do not create your own functional interface without a reason. If a signature is well described by the standard Predicate, Function, Consumer, or Supplier, it is usually better to use the existing type.