Functional Interfaces and Lambda Patterns
Use built-in and custom functional interfaces: Predicate, Function, Consumer, Supplier, and composition.
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
public class FunctionalDemo {
// Predicate — test condition
static final Predicate<String> isNotBlank = s -> s != null && !s.isBlank();
static final Predicate<Integer> isAdult = age -> age >= 18;
static final Predicate<Integer> isSenior = age -> age >= 65;
// Compose predicates
static final Predicate<Integer> isWorkingAge = isAdult.and(isSenior.negate());
// Function — transform input
static final Function<String, String> trim = String::trim;
static final Function<String, String> lower = String::toLowerCase;
static final Function<String, String> normalize = trim.andThen(lower);
// BiFunction — two inputs
static final BiFunction<String, String, String> fullName =
(first, last) -> first + " " + last;
// Custom functional interface
@FunctionalInterface
interface Validator<T> {
boolean validate(T value);
default Validator<T> and(Validator<T> other) {
return t -> this.validate(t) && other.validate(t);
}
default Validator<T> or(Validator<T> other) {
return t -> this.validate(t) || other.validate(t);
}
}
// Pipeline pattern
static <T> T pipe(T input, List<UnaryOperator<T>> transforms) {
T result = input;
for (var fn : transforms) {
result = fn.apply(result);
}
return result;
}
public static void main(String[] args) {
// Filter with composed predicate
List<Integer> ages = List.of(15, 22, 35, 67, 70, 45);
List<Integer> working = ages.stream()
.filter(isWorkingAge)
.toList();
System.out.println(working); // [22, 35, 45]
// Transform pipeline
String result = normalize.apply(" Hello World ");
System.out.println(result); // hello world
// Supplier — lazy initialization
Supplier<List<String>> lazyList = ArrayList::new;
// Consumer — side effects
Consumer<String> logger = msg -> System.out.println("LOG: " + msg);
Consumer<String> alerter = msg -> System.out.println("ALERT: " + msg);
Consumer<String> both = logger.andThen(alerter);
both.accept("Something happened");
}
}Use Cases
- Composable validation and filtering logic
- Data transformation pipelines
- Callback-based API design
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
Method References — All Four Types
Use method references for concise lambda expressions: static, instance, arbitrary object, and constructor.
Best for: Concise functional-style code with method references
Checked Exception Wrapping Utilities
Wrap checked exceptions for use in lambdas and streams: sneaky throws, functional wrappers, and Either.
Best for: Using checked-exception methods in lambda expressions
Java Streams — Filter, Map, Collect
Process collections with Java Streams: filter, map, flatMap, reduce, and collect to lists or maps.
Best for: Transforming and filtering collections
Strategy Pattern with Lambdas
Implement the Strategy pattern using interfaces and Java lambdas for flexible algorithm selection.
Best for: Swappable pricing or discount algorithms