Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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.0

Function 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
true

Consumer 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!
true

Other 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]
7

There 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: 42

Common 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 Calculator

Keep one abstract method in the interface and make the others default — or split it into two interfaces.

Thanks for reading!

Donate to the site — we work hard for you.