Topic 11. Functional Interfaces and Lambdas
Functional interfaces and lambdas in Java let you pass behavior as a value. They do not replace OOP; they complement it by making strategy, callback, and transformation logic more concise. For beginners, the main benefit is less boilerplate and clearer behavioral contracts.
1. Core SAM Concept
A functional interface has exactly one abstract method (SAM).
@FunctionalInterface
interface MathOp {
int apply(int a, int b);
}
MathOp add = (a, b) -> a + b;
System.out.println(add.apply(2, 3)); // 5
@FunctionalInterface is optional but useful: compiler enforces SAM structure.
2. Where It Appears in Real Java
- Collections API:
sort,removeIf,forEach. - Stream API:
map,filter,reduce. CompletableFuturechains.- Strategy and callback patterns.
Runnable,Callable, executors.
3. Standard Functional Interfaces
| Interface | Contract |
|---|---|
Predicate<T> | T -> boolean |
Function<T, R> | T -> R |
Consumer<T> | T -> void |
Supplier<T> | () -> T |
UnaryOperator<T> | T -> T |
BinaryOperator<T> | (T, T) -> T |
BiFunction<T, U, R> | (T, U) -> R |
Example:
Predicate<String> longName = s -> s.length() >= 4;
Function<String, String> upper = String::toUpperCase;
Consumer<String> printer = System.out::println;
List.of("ann", "alex").stream()
.filter(longName)
.map(upper)
.forEach(printer);
4. Lambda Syntax Patterns
() -> 42
x -> x * 2
(a, b) -> a + b
(String s) -> s.trim()
(a, b) -> {
int r = a + b;
return r;
}
Readability rule: if lambda gets long, extract a named method.
5. Method References
When lambda only delegates to an existing method, method reference is cleaner:
List<String> names = new ArrayList<>(List.of("Bob", "Ann", "alex"));
names.sort(String::compareToIgnoreCase);
Forms:
ClassName::staticMethodobj::instanceMethodClassName::instanceMethodClassName::new
6. Variable Capture and Effectively Final
Lambdas may capture local variables only if they are final or effectively final:
int min = 3;
Predicate<String> p = s -> s.length() >= min;
Invalid:
int min = 3;
min++; // no longer effectively final
This avoids confusing lifecycle and concurrency behavior.
7. Function and Predicate Composition
Function<String, String> trim = String::trim;
Function<String, String> upper = String::toUpperCase;
Function<String, String> pipeline = trim.andThen(upper);
System.out.println(pipeline.apply(" java ")); // JAVA
Predicate<String> notBlank = s -> !s.isBlank();
Predicate<String> shortWord = s -> s.length() <= 5;
Predicate<String> rule = notBlank.and(shortWord);
Composition helps build reusable, testable business rules.
8. Practical Patterns
Strategy
interface DiscountStrategy {
double apply(double amount);
}
double checkout(double amount, DiscountStrategy strategy) {
return strategy.apply(amount);
}
double result = checkout(1000, a -> a * 0.9);
Callback wrapper
void withLogging(String opName, Runnable action) {
long start = System.nanoTime();
try {
action.run();
} finally {
System.out.println(opName + " took " + (System.nanoTime() - start));
}
}
9. Frequent Mistakes
- Long lambdas with dense business logic.
- Side effects hidden inside stream chains.
- Unclear lambda argument names in non-trivial expressions.
- Creating custom interfaces when
java.util.functionalready fits.
10. When a Regular Method Is Better
Extract a named method when:
- logic is reused,
- lambda becomes too long,
- logic needs focused unit tests,
- method name communicates intent better than inline expression.
Key Takeaway
Functional interfaces define behavior contracts; lambdas provide compact implementations. The model works best with short focused lambdas, clear naming, and disciplined separation of complex logic into named methods.