Chapter 20: Lambda Expressions & Functional Interfaces (Java 8+)
one of the most loved and most used features in modern Java. They make your code shorter, cleaner, more readable, and open the door to functional programming style — especially when working with Streams, Collections, GUIs, and asynchronous code.
Imagine we’re sitting together in a cozy Mumbai café — it’s evening, the rain has just started outside, and I’m going to explain lambdas like I’m teaching my younger brother who just discovered how much easier life becomes after lambdas.
We’ll go super slowly, with real-life analogies, complete runnable programs, step-by-step breakdowns, tables, common mistakes with fixes, and tons of examples you can copy-paste and run right now.
Let’s dive in!
1. What is a Lambda Expression? (The Big Idea)
A lambda expression is a short, anonymous function — a way to write a method without giving it a name and without declaring a whole class.
Before Java 8 (old verbose way):
|
0 1 2 3 4 5 6 7 8 9 10 11 |
Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello from old anonymous class!"); } }; |
With Lambda (Java 8+) — clean & short:
|
0 1 2 3 4 5 6 |
Runnable r = () -> System.out.println("Hello from lambda!"); |
Real-life analogy: Think of a remote control:
- Old way: You need a full instruction manual (anonymous class) to tell the TV “turn on”.
- Lambda way: You just press one button (() -> turnOn()) — same result, much less code!
2. Lambda Syntax (All Possible Forms)
Basic structure:
|
0 1 2 3 4 5 6 |
(parameters) -> { body } |
Variations:
| Form | Syntax Example | When to Use |
|---|---|---|
| No parameters | () -> System.out.println(“Hi”) | Simple actions with no input |
| One parameter (no parentheses) | name -> System.out.println(“Hello ” + name) | Single argument — cleanest form |
| Multiple parameters | (a, b) -> a + b | Normal math/logic operations |
| With explicit types | (int a, int b) -> a + b | When compiler needs help (rare) |
| With body & return | (a, b) -> { return a + b; } | Multiple lines or complex logic |
| Single expression (implicit return) | (a, b) -> a + b | Short & sweet — most common |
Example 1: All Forms in Action
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import java.util.function.*; public class LambdaSyntaxDemo { public static void main(String[] args) { // 1. No parameters Runnable sayHello = () -> System.out.println("Hello Webliance!"); // 2. One parameter (no parentheses) Consumer<String> greet = name -> System.out.println("Namaste " + name + "!"); // 3. Multiple parameters BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b; // 4. With block body & explicit return Function<String, Integer> length = (str) -> { System.out.println("Calculating length of: " + str); return str.length(); }; // Test them sayHello.run(); greet.accept("Mumbai"); System.out.println("5 + 7 = " + add.apply(5, 7)); System.out.println("Length of 'Java': " + length.apply("Java")); } } |
Output:
|
0 1 2 3 4 5 6 7 8 9 10 |
Hello Webliance! Namaste Mumbai! 5 + 7 = 12 Calculating length of: Java Length of 'Java': 4 |
3. Functional Interfaces (The Heart of Lambdas)
A functional interface is an interface that has exactly one abstract method (can have default/static methods).
Java provides many built-in functional interfaces in java.util.function:
| Interface | Abstract Method Signature | Purpose / Common Use | Example Lambda |
|---|---|---|---|
| Predicate<T> | boolean test(T t) | Check a condition → true/false | x -> x > 18 |
| Consumer<T> | void accept(T t) | Perform action on object (no return) | name -> System.out.println(name) |
| Function<T,R> | R apply(T t) | Transform input to output | str -> str.toUpperCase() |
| Supplier<T> | T get() | Produce a value (no input) | () -> “Hello ” + Math.random() |
| BiFunction<T,U,R> | R apply(T t, U u) | Take two inputs, produce one output | (a,b) -> a + b |
Example 2: Using Built-in Functional Interfaces
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import java.util.function.*; public class FunctionalInterfaceDemo { public static void main(String[] args) { // Predicate: Check condition Predicate<Integer> isAdult = age -> age >= 18; System.out.println("25 is adult? " + isAdult.test(25)); // true System.out.println("15 is adult? " + isAdult.test(15)); // false // Consumer: Perform action Consumer<String> printWelcome = name -> System.out.println("Welcome, " + name + "!"); printWelcome.accept("Webliance"); // Function: Transform Function<String, Integer> strLength = str -> str.length(); System.out.println("Length of 'Mumbai': " + strLength.apply("Mumbai")); // 6 // Supplier: Generate value Supplier<Double> randomNum = () -> Math.random() * 100; System.out.println("Random number: " + randomNum.get()); } } |
4. Method References (Even Shorter Lambdas)
Method references are a shorthand for lambdas when you just want to call an existing method.
Syntax: ClassName::methodName or object::methodName
| Type | Syntax Example | Equivalent Lambda |
|---|---|---|
| Static method | Math::max | (a,b) -> Math.max(a,b) |
| Instance method (object) | str::toUpperCase | () -> str.toUpperCase() |
| Instance method (class) | String::toUpperCase | str -> str.toUpperCase() |
| Constructor | ArrayList::new | () -> new ArrayList<>() |
Example 3: Method References in Action
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import java.util.Arrays; import java.util.List; public class MethodReferenceDemo { public static void main(String[] args) { List<String> names = Arrays.asList("webliance", "amit", "priya"); // Lambda names.forEach(name -> System.out.println(name.toUpperCase())); // Method Reference (same thing, shorter!) names.forEach(System.out::println); // prints original names.forEach(String::toUpperCase); // prints uppercase // Constructor reference Supplier<List<String>> listSupplier = ArrayList::new; List<String> newList = listSupplier.get(); } } |
5. Quick Recap Table (Your Cheat Sheet)
| Concept | Syntax / Example | Purpose / Benefit |
|---|---|---|
| Lambda Expression | (a, b) -> a + b | Short anonymous function |
| Functional Interface | Interface with exactly one abstract method | Target type for lambdas |
| Built-in FI | Predicate<T>, Consumer<T>, Function<T,R>, etc. | Ready-to-use for common tasks |
| Method Reference | Class::method or obj::method | Even shorter lambdas |
| Bounded Wildcard | <? extends Number> | Safe reading from collections |
6. Common Mistakes & Fixes
| Mistake | Problem | Fix |
|---|---|---|
| Using lambda with non-functional interface | Compile error | Ensure interface has exactly one abstract method |
| Forgetting parentheses for no params | -> System.out.println(“Hi”) → error | Use () -> … |
| Mixing varargs with lambdas incorrectly | Confusing syntax | Use (String… names) -> … |
| Trying to modify external variables inside lambda | Compile error (unless effectively final) | Use final or effectively final variables |
| Using raw types with lambdas | Lose type safety | Always use generics: Predicate<String> |
7. Homework for You (Practice to Master!)
- Basic: Create a Predicate<String> that checks if a string is longer than 5 characters. Test with test().
- Medium: Use Consumer<String> to print names in uppercase using method reference.
- Advanced: Create a generic method <T> void process(List<T> list, Consumer<T> action) and use it with lambda and method reference.
- Fun: Use Supplier<String> to generate random greetings.
- Challenge: Sort a list of strings by length using lambda in Collections.sort() or List.sort().
You’re doing amazing! Lambdas & functional interfaces are the foundation of modern Java (Streams, Optional, CompletableFuture, Spring Boot, etc.) — now you’re writing clean, modern, professional code.
