Skip to content
Theory · Lambdas and the Stream API · lesson 3 of 12

Function, Predicate and method references

In the previous lesson we declared our own Rule interface for an instruction card. You will not need an interface for every card.

The factory printed standard card forms long ago:

  • "take a box and hand back another one instead";
  • "take a box and answer: yes or no";
  • "take a box and do something with it";
  • "hand out a new box".

All the foreman has to do is fill in the rule. In Java, these forms live in the java.util.function package:

JAVA
import java.util.function.Function;
import java.util.function.Predicate;

public class Main {
    public static void main(String[] args) {
        Function<String, Integer> len = s -> s.length();
        Predicate<String> isBlank = s -> s.isBlank();
        System.out.println(len.apply("lambda")); // 6
        System.out.println(isBlank.test(""));    // true
    }
}

Let's go through the program line by line:

  1. Function<String, Integer> len is the form "take a String, return an Integer". The angle brackets hold first the type the lambda takes, then the type of the result.
  2. Predicate<String> isBlank is the form "take a String, answer true or false".
  3. len.apply("lambda") runs the lambda and prints 6: the single method of Function is called apply.
  4. isBlank.test("") prints true: the method of Predicate is called test.

Only reference types go in angle brackets, so Integer stands in place of int, as in List<Integer>.

Four ready-made interfaces

All four are functional: each has one abstract method, and the compiler fits a lambda to it. Interviewers often ask you to list them, so remember the method name of each. The letters T and R below are placeholders for types: you write String, Integer or any other reference type in their place.

Function

Function<T, R> takes a value of type T and returns a value of type R. The method is apply. You pick this form when one value must turn into another: a string into its length, a price into a discounted price.

Predicate

Predicate<T> takes a value and returns a boolean. The method is test. It is a "fits or not" check, and it is exactly what removeIf from the previous lesson expects.

Consumer

Consumer<T> takes a value and returns nothing. The method is accept. It is an action with no result: print, write down. forEach expects one.

Supplier

Supplier<T> takes nothing and returns a value. The method is get. Its lambda is written with empty parentheses:

JAVA
Consumer<String> shout = s -> System.out.println(s + "!");
Supplier<String> greeting = () -> "hello";
shout.accept(greeting.get()); // hello!

Here greeting.get() hands out the string "hello", and shout.accept(...) prints it with an exclamation mark. Consumer and Supplier are imported from the same java.util.function package as Function.

Method references

Often a lambda only calls one existing method: s -> s.length(). Such a lambda has a shorter form, a method reference: String::length. Left of :: is a class or an object, right of it is the method's name, with no parentheses and no arguments.

Two forms come up most often:

  • String::length instead of s -> s.length(): the method is called on the parameter itself;
  • System.out::println instead of x -> System.out.println(x): the parameter is passed to a method of the System.out object.

That is why printing a whole list is often written as words.forEach(System.out::println). A method reference is the same card, only instead of instruction text it carries the name of an existing method.

The andThen chain

Besides apply, Function has a default method andThen, an interface method with a body, as in the interfaces theme. The call f.andThen(g) returns a new function: f works first, and g receives its result.

JAVA
Function<String, String> strip = String::strip;
Function<String, Integer> size = String::length;
Function<String, Integer> strippedSize = strip.andThen(size);
System.out.println(strippedSize.apply("  hi  ")); // 2

Line by line:

  1. strip removes the spaces at both ends of a string, and size counts its length. Both functions are written as method references.
  2. strip.andThen(size) builds a new function: first strip, then size on the result of strip.
  3. apply(" hi ") turns " hi " into "hi", and "hi" into 2.

The order reads left to right, like stations on a belt: the function andThen is called on works first, and the one in parentheses works second. Function stays functional all the same: a default method already has a body, and the interface still has one abstract method.

This is where the form picture is inaccurate. A paper form can be filled in any way you like, but the compiler checks a lambda. If a lambda for a Predicate returns something other than a boolean, the program does not compile.

Common mistake

A lambda stored in a variable was called like an ordinary method:

JAVA
Function<String, Integer> len = s -> s.length();
System.out.println(len("lambda")); // ✗
TEXT
error: cannot find symbol

The program does not compile: javac looks for a method len in the Main class and finds none. len is a variable holding an object, and the lambda is called through the method of its interface: len.apply("lambda").

Key points

  • Function turns a value into another one (apply), and Predicate checks it (test).
  • Consumer does something with a value and returns nothing (accept), and Supplier hands out a value while taking nothing (get).
  • The method reference String::length is a short form of a lambda that only calls that method.
  • f.andThen(g) runs f first, then g with the result of f.