Lambdas and functional interfaces
A lambda is a short anonymous function: (params) -> expression. It implements a functional interface, meaning an interface with exactly one abstract method.
Built-in functional interfaces in java.util.function:
Predicate<T>: takes T, returns boolean (filtering)Function<T, R>: takes T, returns R (transforming)Consumer<T>: takes T, returns nothing (printing, saving)Supplier<T>: takes nothing, returns T (creating)- plus
BiFunction,UnaryOperatorandBinaryOperator
Method references are shorthand when a lambda just calls one method: String::length, System.out::println, User::new.
A lambda can use local variables only if they are effectively final.
Lambda syntax
A lambda is a short anonymous function: parameters, an arrow, then an expression or a block. Parameter types are usually inferred.
Runnable hello = () -> System.out.println("Hi");
Comparator<String> byLength = (a, b) -> Integer.compare(a.length(), b.length());
Function<Integer, Integer> square = x -> x * x;
BinaryOperator<Integer> add = (a, b) -> {
int sum = a + b;
return sum;
};The built-in functional interfaces
java.util.function covers most needs:
Function<T, R>: T in, R out.BiFunctiontakes two inputs.Supplier<T>: nothing in, T out.Consumer<T>: T in, nothing out.Predicate<T>: T in, boolean out.UnaryOperator<T>andBinaryOperator<T>: same type in and out.- Primitive versions such as
IntPredicateandToIntFunctionavoid boxing.
Method references
When a lambda just calls an existing method, a method reference is shorter. There are four kinds:
- Static:
Integer::parseInt - On a specific object:
System.out::println - On any object of a type:
String::toUpperCase - Constructor:
ArrayList::new
List<Integer> ids = texts.stream().map(Integer::parseInt).toList();
names.forEach(System.out::println);
List<String> upper = names.stream().map(String::toUpperCase).toList();
Supplier<List<String>> fresh = ArrayList::new;Capturing variables
A lambda can use local variables from the enclosing method only if they're effectively final (never reassigned). Inside a lambda, this means the enclosing object, unlike in an anonymous class.
int limit = 100; // effectively final
orders.removeIf(o -> o.amount() > limit);
// limit = 200; // would make the lambda above a compile errorComposing functions
Functional interfaces have default methods to combine them: andThen and compose for functions, and, or and negate for predicates, and thenComparing and reversed for comparators.
Predicate<Order> paid = Order::isPaid;
Predicate<Order> big = o -> o.amount() > 1000;
orders.stream().filter(paid.and(big.negate())).toList();
Function<String, String> trim = String::strip;
Function<String, Integer> length = trim.andThen(String::length);Where lambdas show up
Collections (forEach, removeIf, replaceAll, computeIfAbsent, sort), streams, Optional, CompletableFuture, executors and event listeners in frameworks. Once you know lambdas, a lot of modern Java reads naturally.
Example
Predicate<String> isLong = s -> s.length() > 5;
Function<String, Integer> len = String::length;
Consumer<String> print = System.out::println;
Supplier<List<String>> fresh = ArrayList::new;
List<String> words = new ArrayList<>(List.of("stream", "map", "lambda", "var"));
words.removeIf(isLong.negate()); // keep only long words
words.sort(Comparator.comparing(String::length)
.thenComparing(Comparator.reverseOrder()));
words.forEach(print);
@FunctionalInterface
interface Discount { double apply(double price); }
Discount festive = p -> p * 0.8;Old way vs new way
Collections.sort(words, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return Integer.compare(a.length(), b.length());
}
});words.sort(Comparator.comparing(String::length));Common mistake
Changing a local variable inside a lambda (count++) doesn't compile. Use a stream reduction, an AtomicInteger, or restructure the code.
Under the hood
Lambdas are not anonymous classes under the hood. The compiler emits an invokedynamic call that uses LambdaMetafactory to create the implementation at runtime, and lambdas that capture nothing are reused as singletons. Inside a lambda, this refers to the enclosing object, not to the lambda.
Check yourself
Which interface fits x -> x > 10?
How this connects
Know these first
Part of Java from zero, Job-ready backend developer.
Was this lesson helpful?
Finished reading? Mark it complete to track your progress.