Functional interfaces
A lambda in Java always has a type, and that type is a functional interface. A functional interface is an interface with exactly one abstract method. The lambda is the implementation of that single method.
Your own functional interface
Any interface with one abstract method works for a lambda. The @FunctionalInterface annotation is optional, but it asks the compiler to check that there really is just one method:
@FunctionalInterface
interface Discount {
double apply(double price);
}
void main() {
Discount half = price -> price / 2;
Discount minusFive = price -> price - 5;
IO.println(half.apply(40));
IO.println(minusFive.apply(40));
}Output:
20.0
35.0Function and Predicate
You rarely need to write your own interface: the java.util.function package has ready-made ones. Function<T, R> takes a T and returns an R (the apply() method), and Predicate<T> checks a condition and returns a boolean (the test() method):
void main() {
Function<String, Integer> length = s -> s.length();
Predicate<Integer> isAdult = age -> age >= 18;
IO.println(length.apply("Anna"));
IO.println(isAdult.test(15));
IO.println(isAdult.test(30));
}Output:
4
false
trueConsumer and Supplier
Consumer<T> takes a value and returns nothing — it does something: prints, saves (the accept() method). Supplier<T>, on the contrary, takes nothing and produces a value (the get() method):
void main() {
Consumer<String> greet = name -> IO.println("Hello, " + name + "!");
Supplier<List<String>> newList = () -> new ArrayList<>();
greet.accept("Oleg");
List<String> list = newList.get();
IO.println(list.isEmpty());
}Output:
Hello, Oleg!
trueOther standard interfaces
For two arguments there are BiFunction<T, U, R> and BiPredicate<T, U>. If the argument and result types are the same, UnaryOperator<T> and BinaryOperator<T> are used:
void main() {
BiFunction<String, Integer, String> repeat = (s, n) -> s.repeat(n);
UnaryOperator<String> trim = s -> s.strip();
BinaryOperator<Integer> max = (a, b) -> a > b ? a : b;
IO.println(repeat.apply("ha", 3));
IO.println("[" + trim.apply(" text ") + "]");
IO.println(max.apply(7, 4));
}Output:
hahaha
[text]
7There are also versions for primitives, so that numbers are not boxed into objects: IntPredicate, IntFunction, ToIntFunction and others.
Combining
The standard interfaces have default methods for building complex functions out of simple ones. Conditions are joined with and(), or() and negate(), and functions are chained with andThen():
void main() {
Predicate<String> notEmpty = s -> !s.isEmpty();
Predicate<String> short5 = s -> s.length() <= 5;
Predicate<String> goodName = notEmpty.and(short5);
Function<Integer, Integer> doubled = x -> x * 2;
Function<Integer, String> doubledText = doubled.andThen(x -> "Result: " + x);
IO.println(goodName.test("Anna"));
IO.println(goodName.test("Alexandra"));
IO.println(doubledText.apply(21));
}Output:
true
false
Result: 42Common mistake
An interface with two abstract methods cannot be the type of a lambda — Java would not know which one the lambda implements. With the @FunctionalInterface annotation the error shows up right in the declaration:
@FunctionalInterface
interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
}
void main() {
}Output:
Main.java:1: error: Unexpected @FunctionalInterface annotation
@FunctionalInterface
^
Calculator is not a functional interface
multiple non-overriding abstract methods found in interface CalculatorKeep one abstract method in the interface and make the others default — or split it into two interfaces.