Lambda Syntax
Lambda Syntax
Lambda expressions provide a concise way to implement functional interfaces (single abstract method interfaces). They were introduced in Java 8.
Basic syntax:
(parameters) -> expression
(parameters) -> { statements; }
import java.util.*;
public class LambdaSyntaxDemo {
public static void main(String[] args) {
// No parameters
Runnable r = () -> System.out.println("Hello from lambda");
r.run();
// Single parameter (parentheses optional)
java.util.function.Consumer<String> printer = s -> System.out.println(s);
printer.accept("Print me");
// Multiple parameters
java.util.function.BiPredicate<String, String> startsWith =
(s, prefix) -> s.startsWith(prefix);
System.out.println(startsWith.test("Hello", "He")); // true
// Single expression (return implicit)
java.util.function.Function<String, Integer> length = s -> s.length();
System.out.println(length.apply("Hello")); // 5
// Block body (explicit return needed)
java.util.function.Function<String, String> transform = s -> {
String trimmed = s.trim();
return trimmed.toUpperCase();
};
System.out.println(transform.apply(" hello ")); // HELLO
// Multiple lines in block
java.util.function.Function<List<Integer>, Integer> sum = list -> {
int total = 0;
for (int num : list) {
total += num;
};
return total;
};
System.out.println(sum.apply(Arrays.asList(1, 2, 3, 4))); // 10
}
}
Syntax variations:
() -> expr— no paramsx -> expr— one param, no parens(x, y) -> expr— multiple params(x) -> { return x * 2; }— block body with returnx -> x * 2— expression body (implicit return)
vs Anonymous Classes
Lambda vs Anonymous Classes
Lambdas are a more concise alternative to anonymous inner classes for implementing functional interfaces.
import java.util.*;
public class LambdaVsAnonymousDemo {
public static void main(String[] args) {
List<String> names = Arrays.asList("Charlie", "Alice", "Bob", "David");
// Anonymous class - verbose
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
System.out.println("Anonymous: " + names);
// Lambda - concise
names.sort((a, b) -> a.compareTo(b));
System.out.println("Lambda: " + names);
// Even more concise with method reference
names.sort(String::compareTo);
System.out.println("Method ref: " + names);
// Key differences:
// 1. Lambdas are for functional interfaces (SAM)
// 2. Anonymous classes can implement any interface or extend any class
// 3. Lambdas have simplified syntax
// 4. 'this' in lambda refers to the enclosing class
// 5. 'this' in anonymous class refers to the anonymous class
// Lambda with multiple statements
names.sort((a, b) -> {
int lengthCompare = Integer.compare(a.length(), b.length());
if (lengthCompare != 0) return lengthCompare;
return a.compareTo(b);
});
System.out.println("Custom sort: " + names);
// Functional interfaces for common patterns
java.util.function.Predicate<String> isLong = s -> s.length() > 3;
java.util.function.Function<String, Integer> toLength = String::length;
java.util.function.Consumer<String> shout = s -> System.out.println(s.toUpperCase() + "!");
java.util.function.Supplier<String> greet = () -> "Hello!";
System.out.println("Long? " + isLong.test("Hello")); // true
System.out.println("Length: " + toLength.apply("Hi")); // 2
shout.accept("wow"); // WOW!
System.out.println("Greeting: " + greet.get()); // Hello!
}
}
Differences:
| Aspect | Lambda | Anonymous Class |
|---|---|---|
| Interface | Functional only | Any interface/class |
| Syntax | Concise | Verbose |
| 'this' | Enclosing class | Anonymous class |
| Scope | Effectively final vars | Can access local vars |
| Performance | May be invoked directly | Creates a new class |
Effective Final
Effectively Final Variables
Lambdas can only capture local variables that are final or effectively final. A variable is effectively final if it is assigned once and never modified after.
import java.util.*;
import java.util.function.*;
public class EffectiveFinalDemo {
public static void main(String[] args) {
// Effectively final - no explicit final needed
String greeting = "Hello"; // effectively final
Consumer<String> printer = s -> System.out.println(greeting + ", " + s);
printer.accept("World"); // Hello, World
// This would NOT compile:
// int count = 0;
// Consumer<String> inc = s -> count++; // COMPILE ERROR: not final
// Workaround: use array or AtomicInteger
int[] count = {0}; // array reference is final
Consumer<String> inc = s -> count[0]++; // OK - modifying array element
inc.accept("test");
System.out.println("Count: " + count[0]); // 1
// Method parameters are effectively final
processWithCallback("data", result -> {
System.out.println("Processed: " + result);
});
// Capturing loop variables - workaround with index
List<Runnable> runners = new ArrayList<>();
for (int i = 0; i < 5; i++) {
final int index = i; // explicitly final
runners.add(() -> System.out.println("Task " + index));
}
runners.forEach(Runnable::run); // 0 1 2 3 4
// Java 9+ effectively final in for-each
String[] names = {"Alice", "Bob", "Charlie"};
List<Supplier<String>> suppliers = new ArrayList<>();
for (String name : names) { // name is effectively final in each iteration
suppliers.add(() -> name);
}
suppliers.forEach(s -> System.out.println(s.get()));
}
static void processWithCallback(String data, Consumer<String> callback) {
String processed = data.toUpperCase(); // effectively final
callback.accept(processed);
}
}
Key rules:
- A variable captured by a lambda must be final or effectively final
- "Effectively final" means the variable is assigned once and never modified
- Use arrays, AtomicInteger, or wrapper objects for mutable state
- Loop variables with index require final or effectively final workarounds
Use Cases
Lambda Use Cases
Lambdas are used extensively with functional interfaces for callbacks, streams, and functional operations.
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class LambdaUseCasesDemo {
public static void main(String[] args) {
// 1. Event handling / Callbacks
Button button = new Button();
button.setOnClickListener(e -> System.out.println("Button clicked!"));
button.click();
// 2. Sorting
List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
names.sort((a, b) -> a.compareTo(b));
names.sort(Comparator.naturalOrder());
System.out.println("Sorted: " + names);
// 3. Filtering
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("Evens: " + evens);
// 4. Mapping
List<String> upper = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Upper: " + upper);
// 5. Custom thread execution
Thread t = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("Thread: " + i);
}
});
t.start();
// 6. Optional handling
Optional.ofNullable("hello")
.filter(s -> s.length() > 3)
.ifPresent(s -> System.out.println("Long string: " + s));
// 7. Predicate chaining
Predicate<String> startsWithA = s -> s.startsWith("A");
Predicate<String> hasLength3 = s -> s.length() == 3;
Predicate<String> combined = startsWithA.and(hasLength3);
System.out.println("'ABC' matches: " + combined.test("ABC")); // true
System.out.println("'ABCD' matches: " + combined.test("ABCD")); // false
// 8. Function chaining
Function<String, String> trim = String::trim;
Function<String, String> lower = String::toLowerCase;
Function<String, String> process = trim.andThen(lower);
System.out.println("Processed: " + process.apply(" HELLO ")); // hello
// 9. Reducing
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println("Sum: " + sum);
// 10. Creating collections with lambdas
Map<String, Integer> wordMap = Map.of("a", 1, "b", 2, "c", 3);
wordMap.forEach((key, value) ->
System.out.println(key + " = " + value));
}
// Simple Button class for demo
static class Button {
private Consumer<Object> listener;
public void setOnClickListener(Consumer<Object> listener) {
this.listener = listener;
}
public void click() {
if (listener != null) listener.accept(null);
}
}
}
Top use cases:
- Callbacks and event handlers
- Sorting with Comparator
- Stream operations (filter, map, reduce)
- Thread/Runnable creation
- Predicate/Function chaining
- Optional.ifPresent()
Practice Problems
Given a list of strings, sort them by length (shortest first) using a lambda expression. Then sort them alphabetically using a method reference.
Solution
import java.util.*;
public class LambdaSorter {
public static List<String> sortByLength(List<String> strings) {
List<String> result = new ArrayList<>(strings);
result.sort((a, b) -> a.length() - b.length());
return result;
}
public static List<String> sortByNatural(List<String> strings) {
List<String> result = new ArrayList<>(strings);
result.sort(String::compareTo);
return result;
}
}Create a method `executeWithRetry(int maxRetries, Runnable action, Consumer<String> logger)` that executes the action. If it throws an exception, log the error and retry up to maxRetries times.
Solution
public class RetryExecutor {
public static void executeWithRetry(int maxRetries, Runnable action, Consumer<String> logger) {
for (int i = 0; i <= maxRetries; i++) {
try {
action.run();
logger.accept("Attempt " + (i + 1) + " succeeded");
return;
} catch (Exception e) {
logger.accept("Attempt " + (i + 1) + " failed: " + e.getMessage());
}
}
logger.accept("All " + (maxRetries + 1) + " attempts failed");
}
}Quiz
1. What is a lambda expression in Java?
2. What does 'effectively final' mean for lambda variables?
3. Which of these is NOT a valid lambda expression?
4. What is the primary purpose of Java Lambdas?
Flashcards
Question
What is the syntax of a lambda expression?
Click to reveal answer
Answer
(parameters) -> expression for single expression, or (parameters) -> { statements; } for block body. Single parameter can omit parentheses: x -> x * 2.
Question
What is the difference between lambda and anonymous class?
Click to reveal answer
Answer
Lambdas are for functional interfaces only, more concise, 'this' refers to enclosing class, and variables must be effectively final. Anonymous classes can implement any interface or extend classes.
Question
What is effectively final in the context of lambdas?
Click to reveal answer
Answer
A local variable that is assigned once and never modified after initialization. Lambdas can capture effectively final variables. Use arrays or AtomicInteger for mutable state.
Question
What is Java Lambdas?
Click to reveal answer
Answer
Java Lambdas is a key concept in Java programming.
Question
When to use Java Lambdas?
Click to reveal answer
Answer
Use Java Lambdas when building production systems that require reliability, scalability, and maintainability.
Revision Notes
Key Takeaways
- 1.Lambdas provide concise syntax for functional interfaces
- 2.Variables captured must be final or effectively final
- 3.'this' in lambda refers to the enclosing class, not the lambda
- 4.Lambdas enable clean stream operations, callbacks, and sorting
Interview Tips
- •Explain lambda syntax and how it differs from anonymous classes
- •Discuss effectively final and why lambdas capture variables this way
- •Demonstrate lambdas with streams, sorting, and callbacks
- •Know the common functional interfaces: Predicate, Function, Consumer, Supplier
Cheat Sheet
Lambda Expressions Cheat Sheet
Syntax
- No params:
() -> expr - One param:
x -> expr - Multiple params:
(x, y) -> expr - Block body:
(x) -> { return x * 2; }
vs Anonymous Classes
- Functional interfaces only
- More concise
- 'this' = enclosing class
- Variables must be effectively final
Effectively Final
- Assigned once, never modified
- Lambdas capture these variables
- Use arrays/AtomicInteger for mutability
Common Use Cases
- Callbacks and event handlers
- Sorting with Comparator
- Stream operations
- Thread creation
- Predicate/Function chaining