All topics
library
intermediate

Lambda Expressions

Write concise function literals and understand how they relate to functional interfaces, closures, and anonymous classes.

A lambda is a short way to implement an interface that has exactly one abstract method.

Lambda = a sticky note with a quick instruction ('add these two numbers'). Anonymous class = a full letter with letterhead, signature, and envelope for the same instruction.

Key Concepts

1
    Runnable r = () -> print("hi");
    Comparator<String> byLen = (a, b) -> a.length() - b.length();
    Function<Integer, Integer> dbl = x -> x * 2;
2
The forms.
    ()        -> expr          no parameters
    x         -> expr          one parameter, parentheses optional
    (a, b)    -> expr          several
    (int a, int b) -> expr     with explicit types
    (a, b)    -> { ... return v; }    a block needs an explicit return
3
It must be a functional interface — one abstract method. That is what makes the target type unambiguous:
4
    @FunctionalInterface
    interface Validator { boolean test(String s); }
5
    Validator notEmpty = s -> !s.isBlank();
6
Capture is limited to effectively final locals.
    int total = 0;
    list.forEach(x -> total += x);     // will not compile
7
The lambda may outlive the method, so it copies the value rather than sharing the variable. Fields are different — those it can read and write, because it holds the object.
8
    int sum = list.stream().mapToInt(Integer::intValue).sum();     // the fix
9
this means the enclosing object, not the lambda. In an anonymous class this meant the anonymous instance, which is a real behavioural difference when migrating:
this
10
    class A {
      void f() {
        Runnable r = () -> print(this);              // the A
        Runnable s = new Runnable() {
          public void run() { print(this); }         // the Runnable
        };
      }
    }
11
A lambda is not an anonymous class underneath. The compiler emits an invokedynamic instruction, and LambdaMetafactory builds the implementation on first use. So there is no extra .class file per lambda, and a stateless one can be cached and reused.
invokedynamicLambdaMetafactory.class
12
Where a lambda cannot go: extending a class, implementing two methods, or needing a name to refer to itself recursively. Those still want an anonymous or named class.
13
What the interviewer is probing.1. "Why must a lambda capture only effectively final locals?" Probing: lifetime. Stalls: "It is a compiler restriction." Moves up: the lambda may outlive the method frame, so the value is copied; allowing reassignment would make it ambiguous which value was meant.
14
2. "Is a lambda compiled to an anonymous inner class?" Probing: what actually happens. Stalls: "Yes, syntactic sugar." Moves up: no — the compiler emits invokedynamic and LambdaMetafactory builds the implementation at first use, so there is no extra class file.
15
3. "How do you accumulate a total inside forEach?" Probing: the capture restriction in practice. Stalls: "Use a local variable." Moves up: you cannot mutate a local — use a stream reduction or sum, or an accumulator object if the side effect is genuinely needed.
16
4. "What makes an interface usable as a lambda target?" Probing: the functional interface rule. Stalls: "The @FunctionalInterface annotation." Moves up: exactly one abstract method — the annotation only asks the compiler to check, and default and static methods do not count.