Lambda Expressions
A powerful feature that brings functional programming to Java
See the Magic: Code Transformation
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hi");
}
};Runnable r = () -> System.out.println("Hi");What is a Lambda Expression?
Think of a lambda expression as a shortcut for writing small, throwaway functions. Before Java 8, if you wanted to pass behavior (like sorting logic or a click handler) to a method, you had to create an entire class or use verbose anonymous classes.
Lambda expressions let you write that same logic in just one line. They're called "lambda" after the Greek letter λ, which is used in mathematics to represent functions.
💡 Simple Definition:
A lambda is an anonymous function – a function without a name that you can pass around like a value. It's just: parameters → body
The Basic Syntax:
(parameters) → expression
(parameters) → { statements; }Why Should You Use Lambda Expressions?
✅ Benefits:
- Less Code – Write in 1 line what used to take 5-10 lines
- More Readable – Focus on WHAT you're doing, not HOW
- Enables Functional Style – Use map, filter, reduce patterns
- Better APIs – Works seamlessly with Streams, Collections
- Parallel Processing – Easier to write thread-safe code
⚠️ When NOT to Use:
- Complex logic with multiple statements (use regular methods)
- When you need to reuse the same logic multiple times
- When debugging – lambdas can be harder to debug
- When you need meaningful stack traces
Before vs After Lambda: See the Difference
Let's see how lambdas simplify code. Here's the same functionality written both ways:
// OLD WAY: Anonymous Inner Class (Java 7 and before)// This takes 7 lines just to define a simple action!Runnable oldWay =newRunnable(){@Overridepublicvoidrun(){System.out.println("Hello World");}};// Sorting with Comparator - also verboseCollections.sort(names,newComparator<String>(){@Overridepublicintcompare(String a,String b){return a.compareTo(b);}});// NEW WAY: Lambda Expression (Java 8+)// Same thing in just 1 line!Runnable newWay =()->System.out.println("Hello World");// Sorting is now crystal clearCollections.sort(names,(a, b)-> a.compareTo(b));// Or even simpler with method referenceCollections.sort(names,String::compareTo);Lambda Syntax Explained Step by Step
Let's break down every part of lambda syntax so you fully understand it:
// PATTERN 1: No parameters// When your function takes nothingRunnable r =()->System.out.println("Hello");// The () means "no input needed"// PATTERN 2: One parameter (parentheses optional!)Consumer<String> print = s ->System.out.println(s);Consumer<String> print2 =(s)->System.out.println(s);// Also valid// When there's exactly ONE parameter, you can skip ()// PATTERN 3: Multiple parameters (parentheses required)BiFunction<Integer,Integer,Integer> add =(a, b)-> a + b;// Both parameters go inside ()// PATTERN 4: With explicit types (optional but sometimes helpful)BiFunction<Integer,Integer,Integer> multiply =(Integer a,Integer b)-> a * b;// Usually Java can figure out types, but you can specify them// PATTERN 5: Multiple statements (use curly braces + return)BiFunction<Integer,Integer,Integer> divide =(a, b)->{if(b ==0){thrownewArithmeticException("Cannot divide by zero");}return a / b;};// When you have multiple lines, you MUST use {} and return🎯 Quick Rules to Remember:
- Zero params:
()required - One param:
()optional - Multiple params:
()required - One expression: no
{}orreturnneeded - Multiple statements:
{}andreturnrequired
Variable Capture: Using Outside Variables in Lambdas
Lambdas can "capture" and use variables from the surrounding code. But there's an important rule: those variables must be effectively final – meaning you can't change them after the lambda is created.
❓ Why This Rule?
Lambdas might run on different threads at different times. If variables could change, the lambda wouldn't know which version of the variable to use. Making them effectively final prevents this confusion and threading bugs.
publicclassVariableCapture{privateString instanceVar ="instance";// Instance variables are finepublicvoiddemonstrate(){String localVar ="local";// This is effectively final (never reassigned)int counter =0;// ✅ WORKS: Using instance variableRunnable r1 =()->System.out.println(instanceVar);// ✅ WORKS: Using effectively final local variableRunnable r2 =()->System.out.println(localVar);// ❌ ERROR: Can't modify and then use in lambda// counter++; // If you uncomment this...// Runnable r3 = () -> System.out.println(counter); // This fails!// 💡 WORKAROUND: Use an array or AtomicIntegerint[] mutableCounter ={0};// Arrays are objects, reference doesn't changeRunnable r4 =()->{
mutableCounter[0]++;// Modifying content is OK!System.out.println(mutableCounter[0]);};// 💡 BETTER WORKAROUND: AtomicInteger (thread-safe)AtomicInteger safeCounter =newAtomicInteger(0);Runnable r5 =()->System.out.println(safeCounter.incrementAndGet());}}Real-World Use Cases
Here are the most common places you'll use lambda expressions in everyday Java code:
List<String> names =Arrays.asList("Alice","Bob","Charlie","David");// 1. SORTING - Most common use case
names.sort((a, b)-> a.length()- b.length());// Sort by length
names.sort((a, b)-> b.compareTo(a));// Reverse alphabetical// 2. FILTERING COLLECTIONS
names.removeIf(name -> name.startsWith("A"));// Remove names starting with A// 3. ITERATING (forEach)
names.forEach(name ->System.out.println("Hello, "+ name));// 4. STREAM OPERATIONS (filter, map, reduce)List<String> longNames = names.stream().filter(name -> name.length()>3)// Keep only names > 3 chars.map(name -> name.toUpperCase())// Convert to uppercase.collect(Collectors.toList());// Collect results// 5. EVENT HANDLING (Swing/JavaFX)
button.addActionListener(e ->handleClick());
button.setOnAction(event ->openDialog());// 6. THREADINGnewThread(()->{System.out.println("Running in background thread");doHeavyWork();}).start();// 7. OPTIONAL OPERATIONSOptional<String> name =Optional.of("Alice");
name.ifPresent(n ->System.out.println("Found: "+ n));String result = name.orElseGet(()->getDefaultName());💡 Tips, Tricks & Best Practices
✅ DO: Keep Lambdas Short
If your lambda is more than 2-3 lines, extract it to a regular method and use a method reference instead.
// Instead of this complex lambda:
names.forEach(name ->{validate(name);transform(name);save(name);});// Do this:
names.forEach(this::processName);// Method reference to a well-named method✅ DO: Use Method References When Possible
Method references are even cleaner than lambdas for simple cases.
// Lambda
names.forEach(name ->System.out.println(name));// Method reference (cleaner!)
names.forEach(System.out::println);❌ DON'T: Use Lambdas for Side Effects in Streams
Avoid modifying external state inside stream lambdas - it can cause bugs in parallel streams.
// BAD: Modifying external listList<String> results =newArrayList<>();
names.stream().forEach(n -> results.add(n.toUpperCase()));// Don't!// GOOD: Use collect insteadList<String> results = names.stream().map(String::toUpperCase).collect(Collectors.toList());💡 TIP: Debugging Lambdas
Use peek() to debug stream operations:
names.stream().peek(n ->System.out.println("Before filter: "+ n)).filter(n -> n.length()>3).peek(n ->System.out.println("After filter: "+ n)).collect(Collectors.toList());⚠️ Common Mistakes to Avoid
Forgetting that lambdas need a Functional Interface
Lambdas can only be used where a functional interface (interface with exactly one abstract method) is expected.
Trying to use non-final variables
Variables used in lambdas must be effectively final. Use AtomicInteger or arrays as workarounds.
Ignoring exception handling
Lambdas can't throw checked exceptions directly. Wrap in try-catch or create helper methods.
Making lambdas too complex
If your lambda needs more than 3 lines, it's a sign you should extract it to a named method.
📝 Quick Summary
Lambda Expression Is:
- An anonymous function
- A shortcut for implementing functional interfaces
- Syntax:
(params) → expression
Remember:
- Keep lambdas short and simple
- Variables must be effectively final
- Use method references when possible
- Great for Streams, sorting, event handlers