Modern Java APIs — collections, streams, CompletableFuture, comparators — all take lambdas.
A lambda is a compact way to implement a functional interface: an interface with exactly one abstract method. Instead of an anonymous class, you write just the parameters and the body:
Comparator<String> byLength = (a, b) -> Integer.compare(a.length(), b.length());
Runnable task = () -> System.out.println("tick");Parameter types are inferred from the target interface. A single parameter needs no parentheses (s -> s.isBlank()), and a multi-line body uses braces with an explicit return.
java.util.function provides the standard shapes, so you rarely need to declare your own:
Function<T, R>— takes T, returns R (apply).Predicate<T>— takes T, returns boolean (test).Consumer<T>— takes T, returns nothing (accept).Supplier<T>— takes nothing, returns T (get).BiFunction<T, U, R>,UnaryOperator<T>,BinaryOperator<T>, plus primitive variants likeIntPredicatethat avoid boxing.
A method reference is shorthand when a lambda just calls an existing method: String::length, System.out::println, Person::new, this::validate.
Lambdas can read local variables from the enclosing scope only if they are effectively final — never reassigned. This prevents confusing shared mutable state; if you need to accumulate, use a stream reduction or a field instead.
Functional interfaces compose: Predicate.and/or/negate, Function.andThen/compose, Comparator.thenComparing/reversed. Annotate your own with @FunctionalInterface so the compiler enforces the single-abstract-method rule.
Example
import java.util.List;
import java.util.function.*;
public class Lambdas {
public static void main(String[] args) {
Predicate<String> isBlank = String::isBlank;
Predicate<String> hasContent = isBlank.negate();
Function<String, Integer> length = String::length;
Function<String, String> shout = s -> s.toUpperCase() + "!";
Supplier<List<String>> names = () -> List.of("ana", " ", "bo");
BinaryOperator<Integer> add = Integer::sum;
int minLength = 2; // effectively final: captured below
Predicate<String> longEnough = s -> s.length() >= minLength;
for (String n : names.get()) {
if (hasContent.and(longEnough).test(n)) {
System.out.println(shout.andThen(length).apply(n) + " " + shout.apply(n));
}
}
// 4 ANA!
// 3 BO!
System.out.println(add.apply(2, 3)); // 5
}
}Remember
- A lambda implements a single-abstract-method interface
- Know Function, Predicate, Consumer, Supplier and their compositions
- Captured locals must be effectively final
Watch out for
- Trying to increment a local counter inside a lambda — it won't compile because the variable must be effectively final.