Course outline · 0% complete

0/29 lessons0%

Course overview →

Lambdas

lesson 9-1 · ~11 min · 24/29

From lesson 6-3, the Instrument interface declared exactly one method, play. That shape matters here because an interface with a single abstract method can be implemented on the spot by a lambda.

A lambda is a compact implementation of a one-method interface, which Java calls a functional interface. Instead of writing a whole class Piano implements Instrument, you can write:

Instrument piano = () -> System.out.println("plink");

One expression replaces a class declaration, and this lesson is about where that shortcut applies.

Functions as values

Python lets you pass functions around, as in sorted(names, key=len). For its first two decades Java could not, and passing one line of behavior meant declaring a whole class around it.

Here is what "sort by length" cost before 2014, next to the lambda, a compact anonymous function, that Java 8 introduced:

// the old way: an anonymous class, five lines of ceremony for one line of logic
names.sort(new Comparator<String>() {
  public int compare(String a, String b) {
    return a.length() - b.length();
  }
});

// the lambda way
names.sort((a, b) -> a.length() - b.length());

Lambdas exist to make behavior as easy to hand around as data, and the whole streams toolkit in the next lesson is built on that.

The shapes are:

n -> n * 2                 // one parameter, returns n * 2
(a, b) -> a + b            // two parameters
s -> {                     // a multi-statement body needs braces and return
  String t = s.trim();
  return t.length();
}

A lambda's type is always some functional interface, and the collections you already know accept them directly:

names.removeIf(n -> n.length() > 4);        // takes a Predicate<String>
names.forEach(n -> System.out.println(n));  // takes a Consumer<String>
nums.sort((a, b) -> a - b);                 // takes a Comparator<Integer>

Filtering and printing with no loop

removeIf drops the long names and forEach prints what is left.

import java.util.ArrayList;

public class Main {
  public static void main(String[] args) {
    ArrayList<String> names = new ArrayList<>();
    names.add("Ada");
    names.add("Grace");
    names.add("Alan");
    names.add("Barbara");

    names.removeIf(n -> n.length() > 4);
    names.forEach(n -> System.out.println(n.toUpperCase()));
  }
}

Output

ADA
ALAN

Grace has 5 characters and Barbara has 7, so both are removed, while Ada and Alan survive at 3 and 4.

Neither line contains a for, and neither mentions an index. The list knows how to walk itself, and the lambda supplies only the decision, which is the division of labor the next lesson pushes much further.

The core functional interfaces

A lambda always implements some interface with one abstract method, and java.util.function ships the handful that cover nearly every case:

InterfaceShapeTypical use
Predicate<T>T in, boolean outfiltering, removeIf
Function<T, R>T in, R outtransforming, map
Consumer<T>T in, nothing outforEach, printing
Supplier<T>nothing in, T outlazy creation of a value
Comparator<T>two T in, int outsorting
Predicate<String> isLong = s -> s.length() > 4;
Function<String, Integer> len = s -> s.length();
Consumer<String> show = s -> System.out.println(s);

System.out.println(isLong.test("Barbara"));   // true
System.out.println(len.apply("Ada"));         // 3

Each interface names its single method, so a Predicate is invoked with test and a Function with apply. Assigning a lambda to a variable like this is occasionally useful for reuse, though most lambdas are written inline at the call that needs them.

When a lambda merely calls an existing method, a method reference says so more briefly. System.out::println means the same as n -> System.out.println(n), and String::length means s -> s.length().

Three lambdas on one list

Filter, sort, and print, each supplied as a one-line function.

import java.util.ArrayList;

public class Main {
  public static void main(String[] args) {
    ArrayList<Integer> nums = new ArrayList<>();
    nums.add(5);
    nums.add(12);
    nums.add(7);
    nums.add(20);
    nums.add(3);
    nums.removeIf(n -> n % 2 == 0);
    nums.sort((a, b) -> a - b);
    nums.forEach(n -> System.out.println(n));
  }
}

Output

3
5
7

The even test is n % 2 == 0 from lesson 3-1, so removeIf deletes 12 and 20. The comparator returns a negative number when a belongs first, which is the contract that produces an ascending order.

The final line could be written nums.forEach(System.out::println) as a method reference, since the lambda does nothing but forward its argument. Both forms compile to the same thing, and the shorter one is conventional.