Lambda Expressions & Functional Interfaces

Course 2 · Ch 3
Lambda Expressions & Functional Interfaces
A lambda is sugar over java1-6's own interfaces — genuinely comparable to C++'s own operator() reveal

java2-2 used String::length and Comparator.comparing() without pausing on what makes them possible. This chapter is that pause — and a genuine reveal about what a lambda actually is under the hood.

Functional Interfaces — The Prerequisite

@FunctionalInterface public interface Calculator { int apply(int a, int b); // exactly one abstract method — a SAM }

A functional interface is an interface with exactly one abstract method — a Single Abstract Method, or SAM. This directly builds on java1-6's own interface material; default methods don't count against the limit, only abstract ones do. @FunctionalInterface is optional, but when present, the compiler enforces the one-abstract-method rule and flags a violation immediately.

Lambda Expressions — Syntax

Calculator add = (a, b) -> a + b; // expression form Calculator addVerbose = (a, b) -> { // block form int sum = a + b; return sum; }; // the pre-lambda equivalent — an anonymous inner class: Calculator addOldWay = new Calculator() { @Override public int apply(int a, int b) { return a + b; } };

A lambda's parameter types are inferred from the functional interface's own method signature — Calculator already says both parameters are int, so the lambda doesn't repeat it.

Lambdas Are Sugar Over Existing OOP

Here's the reveal: Calculator add = (a, b) -> a + b; is really instantiating an anonymous implementation of Calculator's single method — the compiler generates essentially the same thing as the anonymous-inner-class version shown above. Nothing genuinely new exists in the language's object model; a lambda is compact syntax for a pattern that was already fully expressible in java1-6's own interface system. This is a real, direct parallel to cpp2-7's own reveal — std::cout << turned out to have been using cpp1-6's own operator-overload mechanism the entire time; here, every lambda turns out to have been using java1-6's own interface-implementation mechanism the entire time.

Built-in Functional Interfaces

Function<String, Integer> length = String::length; // T -> R Predicate<String> isEmpty = String::isEmpty; // T -> boolean Consumer<String> print = System.out::println; // T -> void Supplier<String> greeting = () -> "Hello"; // () -> T

java.util.function supplies ready-made functional interfaces for the shapes that come up constantly — Function<T,R>, Predicate<T>, Consumer<T>, Supplier<T> — so most code never needs to declare a custom one at all.

Method References

names.sort(Comparator.comparing(String::length)); // java2-2's own example, now explained // equivalent lambda: names.sort(Comparator.comparing(s -> s.length()));

Class::method is shorthand for a lambda that does nothing but call an existing method — exactly what powered java2-2's own String::length sort. It's the same mechanism as any other lambda, just with the boilerplate parameter-passing removed since the method reference already implies it.

ConceptC++ (cpp1-6 / cpp2-7)Java
The underlying mechanismoperator() overload (a "functor")SAM interface implementation
The "reveal" chaptercpp2-7 — cout << was always operator<<this chapter — a lambda was always an interface impl
Compact syntax added laterC++11 lambdas, sugar over the same ideaJava 8 lambdas, sugar over the same idea
Reach for java.util.function before writing a custom interface
Function, Predicate, Consumer, and Supplier already cover the overwhelming majority of shapes a lambda needs — a custom functional interface is worth writing only when none of the standard shapes genuinely fit.
Captured local variables must be effectively final
A lambda can read a local variable from its enclosing scope, but that variable must never be reassigned anywhere after it's first set — "effectively final," even without the final keyword written explicitly. This is stricter than a JavaScript closure, which can freely mutate a captured variable; attempting the same in a Java lambda simply fails to compile.

Coding Challenges

Challenge 1

Write a functional interface StringTransformer with one abstract method transform(String s), then implement it once as an anonymous inner class and once as a lambda that both uppercase a string, showing both produce the same result.

📄 View solution
Challenge 2

Write code using Predicate<Integer> to check whether a number is even, and Function<Integer, Integer> to square a number, applying both to a list of integers and printing the results.

📄 View solution
Challenge 3

Write a lambda that captures a local int variable, then attempt to reassign that variable after the lambda is defined. Show the resulting compile error and explain why it happens in terms of effectively final.

📄 View solution

Chapter 3 Quick Reference

  • A functional interface has exactly one abstract method (a SAM); default methods don't count, per java1-6
  • A lambda is compact syntax for an anonymous implementation of a functional interface's single method — nothing new in the object model
  • Genuinely comparable to cpp2-7's own reveal: cout << was always operator<<, just as every lambda is always an interface implementation
  • java.util.function supplies Function/Predicate/Consumer/Supplier for the common shapes, avoiding custom interfaces
  • Class::method is shorthand for a lambda that just calls an existing method
  • Captured local variables must be effectively final — stricter than JavaScript's mutable closures
  • Next chapter: the Streams API — functional-style data processing built on these same functional interfaces