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:
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:
Function<String, Integer> lenis the form "take aString, return anInteger". The angle brackets hold first the type the lambda takes, then the type of the result.Predicate<String> isBlankis the form "take aString, answertrueorfalse".len.apply("lambda")runs the lambda and prints6: the single method ofFunctionis calledapply.isBlank.test("")printstrue: the method ofPredicateis calledtest.
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:
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::lengthinstead ofs -> s.length(): the method is called on the parameter itself;System.out::printlninstead ofx -> System.out.println(x): the parameter is passed to a method of theSystem.outobject.
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.
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 ")); // 2Line by line:
stripremoves the spaces at both ends of a string, andsizecounts its length. Both functions are written as method references.strip.andThen(size)builds a new function: firststrip, thensizeon the result ofstrip.apply(" hi ")turns" hi "into"hi", and"hi"into2.
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:
Function<String, Integer> len = s -> s.length();
System.out.println(len("lambda")); // ✗error: cannot find symbolThe 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
Functionturns a value into another one (apply), andPredicatechecks it (test).Consumerdoes something with a value and returns nothing (accept), andSupplierhands out a value while taking nothing (get).- The method reference
String::lengthis a short form of a lambda that only calls that method. f.andThen(g)runsffirst, thengwith the result off.