1. The Hook (The "Byte-Sized" Intro)
- In a Nutshell: Lambda expressions are anonymous functions—methods without names.
- Syntax: (parameters) -> expression or (parameters) -> { statements }. Replace verbose anonymous classes. Require functional interface (single abstract method).
- Variable capture: Can access effectively final variables from enclosing scope.
- Benefits: 70% less code, readable, enables functional programming.
- Use for: Comparators, event handlers, callbacks, Stream API.
- Type inference: Compiler infers parameter types!
Think of vending machine. Anonymous class = build custom machine for each snack (overkill!). Lambda = programmable machine: "dispenseCoke→press button 1", "dispenseWater→press button 2". One machine, configurable behavior!
2. Conceptual Clarity (The "Simple" Tier)
💡 The Analogy: Remote Control
- Before (Anonymous Class): Build entire custom remote for each TV function
- After (Lambda): Universal remote with programmable buttons
- Lambda: The programming ("button1 → volumeUp")
Lambda Evolution
graph LR
A[Anonymous Class<br/>10 lines] -->|Java 8| B[Lambda Expression<br/>1 line]
B -->|Even shorter| C[Method Reference<br/>Class::method]
3. Technical Mastery (The "Deep Dive")
Lambda Syntax Variations
java
// No parameters
() -> System.out.println("Hello")
// One parameter (parentheses optional)
x -> x * 2
(x) -> x * 2 // Same
// Multiple parameters
(x, y) -> x + y
// With block
(x, y) -> {
int sum = x + y;
return sum;
}
// With type declarations (usually inferred)
(int x, int y) -> x + y4. Interactive & Applied Code
java
import java.util.*;
import java.util.function.*;
public class LambdaExpressionsDemo {
public static void main(String[] args) {
demonstrateBasicLambdas();
demonstrateVsAnonymousClass();
demonstrateVariableCapture();
demonstrateCommonUseCases();
}
// Basic lambda syntax
static void demonstrateBasicLambdas() {
System.out.println("=== BASIC LAMBDAS ===");
// Runnable (no parameters)
Runnable r1 = () -> System.out.println("Running!");
r1.run();
// Predicate (one parameter)
Predicate<String> isEmpty = s -> s.isEmpty();
System.out.println("Is empty: " + isEmpty.test(""));
// Comparator (two parameters)
Comparator<Integer> comp = (a, b) -> a - b;
System.out.println("Compare 5 vs 3: " + comp.compare(5, 3));
// With block
Function<Integer, Integer> square = (x) -> {
int result = x * x;
System.out.println("Squaring " + x);
return result;
};
System.out.println("Square of 5: " + square.apply(5));
}
// Lambda vs Anonymous Class
static void demonstrateVsAnonymousClass() {
System.out.println("\n=== LAMBDA VS ANONYMOUS CLASS ===");
// ❌ VERBOSE: Anonymous class (Java 7 and before)
Runnable oldWay = new Runnable() {
@Override
public void run() {
System.out.println("Old way: 5 lines of boilerplate!");
}
};
// ✅ CONCISE: Lambda expression (Java 8+)
Runnable newWay = () -> System.out.println("New way: 1 line!");
oldWay.run();
newWay.run();
// Comparator example
List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
// Old way
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
// New way
names.sort((a, b) -> a.compareTo(b));
System.out.println("Sorted: " + names);
}
// Variable capture (effectively final)
static void demonstrateVariableCapture() {
System.out.println("\n=== VARIABLE CAPTURE ===");
String prefix = "Hello, "; // Effectively final
int multiplier = 2; // Effectively final
// Lambda captures variables from enclosing scope
Consumer<String> greeter = name ->
System.out.println(prefix + name);
Function<Integer, Integer> doubler = x -> x * multiplier;
greeter.accept("Alice");
System.out.println("Double 5: " + doubler.apply(5));
// ❌ COMPILE ERROR: Can't modify captured variables
// prefix = "Hi, "; // Error: variable not effectively final!
// ✅ OK: Local variables inside lambda
Function<Integer, Integer> adder = x -> {
int localVar = 10; // OK to declare
return x + localVar;
};
}
// Common use cases
static void demonstrateCommonUseCases() {
System.out.println("\n=== COMMON USE CASES ===");
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 1. forEach (Consumer)
numbers.forEach(n -> System.out.print(n + " "));
System.out.println();
// 2. Filtering (Predicate)
List<Integer> evens = new ArrayList<>();
numbers.forEach(n -> {
if (n % 2 == 0) {
evens.add(n);
}
});
System.out.println("Evens: " + evens);
// 3. Sorting (Comparator)
List<String> words = Arrays.asList("apple", "pie", "a", "banana");
words.sort((a, b) -> Integer.compare(a.length(), b.length()));
System.out.println("By length: " + words);
// 4. Thread creation (Runnable)
Thread thread = new Thread(() -> {
System.out.println("Running in thread: " + Thread.currentThread().getName());
});
thread.start();
}
}
// Custom functional interface
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
class CustomFunctionalInterfaceDemo {
public static void main(String[] args) {
// Lambda implements custom interface
Calculator add = (a, b) -> a + b;
Calculator multiply = (a, b) -> a * b;
Calculator max = (a, b) -> a > b ? a : b;
System.out.println("5 + 3 = " + add.calculate(5, 3));
System.out.println("5 * 3 = " + multiply.calculate(5, 3));
System.out.println("max(5, 3) = " + max.calculate(5, 3));
}
}5. The Comparison & Decision Layer
| Feature | Anonymous Class | Lambda |
|---|---|---|
| Lines of code | 5-10 | 1 |
| this keyword | Refers to instance | Refers to enclosing class |
| Can implement | Any interface/class | Only functional interfaces |
| Serializable | If class is | Only if target type is |
| Shadowing | Can shadow variables | Cannot shadow |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question:
"What does 'effectively final' mean for lambda variable capture?"
Answer: Variable doesn't need final keyword, but can't be modified after initialization!
java
int x = 10; // Not declared final
// Lambda captures x
Runnable r = () -> System.out.println(x);
// ❌ COMPILE ERROR: Can't modify captured variable
// x = 20; // Error: variable x must be effectively final
// Why? Lambda may execute later (different context).
// If x changed, which value should lambda see?Workaround:
java
final int[] x = {10}; // Array is final, contents mutable
Runnable r = () -> System.out.println(x[0]);
x[0] = 20; // ✅ OK: Modifying array contentsPro-Tips:
- Type inference:
java
// ❌ Verbose: Explicit types
Comparator<String> c = (String a, String b) -> a.compareTo(b);
// ✅ Concise: Inferred types
Comparator<String> c = (a, b) -> a.compareTo(b);- Return statement optional for single expression:
java
// ❌ Unnecessary return
Function<Integer, Integer> f = x -> { return x * 2; };
// ✅ Implicit return
Function<Integer, Integer> f = x -> x * 2;- Parentheses optional for single parameter:
java
Consumer<String> c1 = (s) -> System.out.println(s);
Consumer<String> c2 = s -> System.out.println(s); // ✅ Cleaner