Lambdas
Java lambdas are anonymous functions matching a functional interface. Pair with method references and the Streams API for declarative collection processing.
Functional interfaces, methods refs, capture
EXAMPLE
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
// 1) Basic lambda
Runnable r = () -> System.out.println("hi");
r.run();
Function<Integer, Integer> square = x -> x * x;
square.apply(5); // 25
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
add.apply(2, 3); // 5
Predicate<String> isLong = s -> s.length() > 8;
isLong.test("hello"); // false
Consumer<String> print = System.out::println;
print.accept("hi");
Supplier<List<String>> mk = ArrayList::new;
List<String> list = mk.get();
// 2) Functional interfaces — the contracts
// Runnable : void ()
// Callable<V> : V () — throws Exception
// Supplier<T> : T ()
// Consumer<T> : void (T)
// BiConsumer<T,U>: void (T, U)
// Predicate<T> : boolean (T)
// Function<T,R> : R (T)
// BiFunction<T,U,R> : R (T, U)
// UnaryOperator<T> : T (T)
// BinaryOperator<T> : T (T, T)
// 3) Method references
Function<String, Integer> len = String::length; // instance method on class
Function<List<?>, Integer> size = List::size;
BiFunction<String, String, String> concat = String::concat; // bound to first arg
Consumer<String> printer = System.out::println; // bound instance method
Supplier<List<String>> ctor = ArrayList::new; // constructor reference
// 4) Lambda + Stream — the daily driver
List<Integer> nums = List.of(1, 2, 3, 4, 5);
int sum = nums.stream().mapToInt(Integer::intValue).sum();
List<Integer> squares = nums.stream().map(n -> n * n).toList();
List<Integer> evens = nums.stream().filter(n -> n % 2 == 0).toList();
Optional<Integer> max = nums.stream().max(Integer::compare);
Map<Boolean, List<Integer>> partitioned = nums.stream().collect(Collectors.partitioningBy(n -> n > 2));
// 5) Capture — lambdas can use surrounding variables (effectively final)
int base = 10;
Function<Integer, Integer> plusBase = x -> x + base;
// base = 20; // ILLEGAL — captured variable must be effectively final
// To 'capture mutable state', use an array/AtomicInteger/lambda-local class
int[] counter = {0};
nums.forEach(n -> counter[0]++);
// 6) Lambda in a sort
List<String> names = new ArrayList<>(List.of("Cy", "Ada", "Bo"));
names.sort((a, b) -> a.length() - b.length());
names.sort(Comparator.comparingInt(String::length));
names.sort(Comparator.comparing(String::length).thenComparing(Comparator.naturalOrder()));
// 7) Multi-line / block lambdas
Function<String, String> upperGreet = s -> {
if (s == null || s.isBlank()) return "Hello, stranger";
return "Hello, " + s.trim().toUpperCase();
};
// 8) Define your own functional interface
@@FunctionalInterface
interface TriFunction<A, B, C, R> {
R apply(A a, B b, C c);
}
TriFunction<Integer, Integer, Integer, Integer> add3 = (a, b, c) -> a + b + c;
// 9) Higher-order — functions that take or return lambdas
static <T> List<T> filter(List<T> in, Predicate<T> p) {
var out = new ArrayList<T>();
for (var x : in) if (p.test(x)) out.add(x);
return out;
}
filter(nums, n -> n > 2);
// 10) Composition
Function<Integer, Integer> plus1 = x -> x + 1;
Function<Integer, Integer> times2 = x -> x * 2;
plus1.andThen(times2).apply(3); // (3+1)*2 = 8
plus1.compose(times2).apply(3); // (3*2)+1 = 7
Predicate<Integer> small = n -> n < 5;
Predicate<Integer> even = n -> n % 2 == 0;
small.and(even).test(4); // true
small.or(even).test(7); // false
small.negate().test(6); // true
// 11) Optional + lambda
Optional<String> name = Optional.of("Ada");
name.map(String::toUpperCase).ifPresent(System.out::println);
String display = name.orElseGet(() -> "Anonymous");
// 12) Exception handling in lambdas (clunky for checked)
Function<String, Integer> parse = s -> {
try { return Integer.parseInt(s); }
catch (NumberFormatException e) { return 0; }
};
// Helper to bridge — wrap a throwing lambda
@@FunctionalInterface
interface ThrowingFunction<T, R, E extends Exception> {
R apply(T t) throws E;
}
static <T, R, E extends Exception> Function<T, R> unchecked(ThrowingFunction<T, R, E> f) {
return t -> {
try { return f.apply(t); }
catch (Exception e) { throw new RuntimeException(e); }
};
}
List<Integer> parsed = strings.stream().map(unchecked(Integer::parseInt)).toList();
// 13) Common pitfalls
// • Capturing 'this' implicitly — keeps the enclosing object from GC; risky in long-lived lambdas
// • Forgetting effectively-final → won't compile
// • Using lambdas instead of for loops in HOT inner loops — JIT usually handles it, but profile
// • Lambdas with checked exceptions — wrap or rethrow as runtime
Why it matters
Method references (String::length, System.out::println) are the cleanest lambda form. Combined with Streams, the “Java is verbose” trope mostly disappears.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
Runnable hi = () -> System.out.println("hi");
hi.run();
List<String> xs = List.of("b","a");
xs.stream().sorted().forEach(System.out::println);
Try it Yourself »
Discussion
Loading…