Lesson 22 • Advanced
Lambda Expressions Deep Dive
Functional interfaces, method references, closures, and lambda best practices.
Before You Start
You should understand Interfaces (Lesson 11), Anonymous Classes (Lesson 19), and Streams API (Lesson 21). Lambdas are the building blocks of functional Java.
What You'll Learn
- ✅ Lambda syntax and type inference
- ✅ Built-in functional interfaces: Predicate, Function, Consumer, Supplier
- ✅ Method references (::) — four types
- ✅ Composing functions and predicates
- ✅ Effectively final variables and closures
- ✅ Lambda vs anonymous class differences
1️⃣ Lambdas: Functions as First-Class Citizens
Before Java 8, passing behavior required verbose anonymous inner classes. Lambdas changed everything — they let you write (x, y) → x + y instead of a 5-line anonymous class. Under the hood, lambdas aren't syntactic sugar for anonymous classes; they use invokedynamic and are more efficient.
💡 Analogy: Sticky Notes
A lambda is like a sticky note with instructions. Instead of writing a formal letter (anonymous class) every time you want to tell someone what to do, you scribble a quick note: "sort by price" or "filter active users." The recipient (the method accepting the lambda) follows the note.
Try It: Lambda Syntax & Functional Interfaces
// Lambda Expressions — Syntax & Functional Interfaces
console.log("=== Lambda Syntax ===\n");
// 1. Lambda syntax variations
console.log("1. LAMBDA SYNTAX:");
let greet = () => console.log(" Hello!");
let square = x => x * x;
let add = (a, b) => a + b;
let process = (x) => { let result = x * 2; return result + 1; };
greet();
console.log(" square(5) =", square(5));
console.log(" add(3, 4) =", add(3, 4));
console.log(" process(5) =", process(5));
// 2. Functional Interfaces (simulated)
con
...2️⃣ Method References & Composition
Method references (::) are shorthand for lambdas that just call an existing method. There are four types: static (Math::abs), instance on object (str::length), instance on class (String::toUpperCase), and constructor (ArrayList::new). Use them whenever a lambda simply delegates to an existing method.
3️⃣ Composing Functions
Java's functional interfaces support composition. Function.compose() and Function.andThen() chain transformations. Predicate.and(), .or(), and .negate() combine boolean tests. This creates readable, reusable logic from small building blocks.
Try It: Method References & Composition
// Method References & Function Composition
console.log("=== Method References & Composition ===\n");
// 1. Method references (simulated)
console.log("1. METHOD REFERENCES:");
let words = ["Hello", "World", "Java"];
let upper = words.map(s => s.toUpperCase());
console.log(" String::toUpperCase →", upper.join(", "));
// Constructor reference
class Point {
constructor(x, y) { this.x = x; this.y = y; }
toString() { return "(" + this.x + "," + this.y + ")"; }
}
let coords = [[1,2], [3,4],
...4️⃣ Closures & Effectively Final
Lambdas can capture local variables, but those variables must be effectively final — assigned exactly once. This restriction exists because lambdas might execute on a different thread than where the variable was declared. If you need a mutable counter inside a lambda, use AtomicInteger or an array wrapper.
Try It: Closures & Pipeline Builder
// Closures & Pipeline Builder Pattern
console.log("=== Closures & Pipelines ===\n");
// 1. Closures
console.log("1. CLOSURES (Effectively Final):");
function createCounter(start) {
let count = start;
return {
increment: () => ++count,
get: () => count,
reset: () => { count = start; }
};
}
let counter = createCounter(10);
console.log(" Start:", counter.get());
counter.increment();
counter.increment();
console.log(" After 2 increments:", counter.get());
cou
...Common Mistakes
int count = 0; list.forEach(x → count++); won't compile. Use AtomicInteger or int[] count = {0}.x → x.toString() should be Object::toString. Method references are more readable and slightly faster.Pro Tips
💡 Predicate chaining is powerful: isAdult.and(isActive).negate() reads like English and is fully composable.
💡 Function.compose() vs andThen(): f.compose(g) applies g first, then f. f.andThen(g) applies f first, then g.
💡 Lambdas vs anonymous classes: this inside a lambda refers to the enclosing class, not the lambda itself.
📋 Quick Reference
| Interface | Method | Signature |
|---|---|---|
| Predicate<T> | test() | T → boolean |
| Function<T,R> | apply() | T → R |
| Consumer<T> | accept() | T → void |
| Supplier<T> | get() | () → T |
| BiFunction<T,U,R> | apply() | (T, U) → R |
🎉 Lesson Complete!
You've mastered lambda expressions, functional interfaces, and function composition!
Next: Working with Optionals — eliminate null pointer exceptions with Optional chains.
Sign up for free to track which lessons you've completed and get learning reminders.