Lambdas & Functional Interfaces
A lambda is a short, anonymous function you can pass around like a value: "sort by price", "keep only adults", "when clicked, do this". Lambdas (Java 8+) work wherever a functional interface — an interface with exactly one abstract method — is expected. They're the foundation of streams and modern Java APIs.
From anonymous class to lambda
import java.util.ArrayList; import java.util.Comparator; import java.util.List; public class Main { public static void main(String[] args) { List<String> words = new ArrayList<>(List.of("banana", "fig", "apple")); words.sort(new Comparator<String>() { @Override public int compare(String a, String b) { return a.length() - b.length(); } }); System.out.println(words); words.sort((a, b) -> b.length() - a.length()); System.out.println(words); } }
[fig, apple, banana] [banana, apple, fig]
Both sorts pass "a way to compare two strings". The lambda keeps only what matters: the parameters, an arrow, and the body.
Lambda syntax
| Form | Example |
|---|---|
| No parameters | () -> "hello" |
| One parameter (no parentheses needed) | x -> x * 2 |
| Several parameters | (a, b) -> a + b |
| Explicit types | (String s) -> s.length() |
| Block body | x -> { int y = x * 2; return y + 1; } |
Parameter types are usually inferred from the context. An expression body returns its value automatically; a block body needs return.
Built-in functional interfaces
import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.function.UnaryOperator; public class Main { public static void main(String[] args) { Function<String, Integer> length = s -> s.length(); Predicate<Integer> isEven = n -> n % 2 == 0; Consumer<String> shout = s -> System.out.println(s.toUpperCase() + "!"); Supplier<String> greeting = () -> "Hello"; BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b; UnaryOperator<String> trim = String::strip; System.out.println(length.apply("lambda")); System.out.println(isEven.test(7)); shout.accept("done"); System.out.println(greeting.get()); System.out.println(multiply.apply(6, 7)); System.out.println("[" + trim.apply(" hi ") + "]"); } }
6 false DONE! Hello 42 [hi]
| Interface | Takes → returns | Method |
|---|---|---|
Function<T, R> | T → R | apply |
Predicate<T> | T → boolean | test |
Consumer<T> | T → nothing | accept |
Supplier<T> | nothing → T | get |
BiFunction<T, U, R> | T, U → R | apply |
UnaryOperator<T>, BinaryOperator<T> | T → T, (T, T) → T | apply |
The java.util.function package also has primitive versions (IntPredicate, ToIntFunction…) that avoid boxing.
Method references
import java.util.List; import java.util.function.Function; import java.util.function.Supplier; public class Main { public static void main(String[] args) { List<String> names = List.of("ana", "ben", "mei"); names.forEach(System.out::println); Function<String, String> upper = String::toUpperCase; Function<String, Integer> parse = Integer::parseInt; Supplier<StringBuilder> maker = StringBuilder::new; System.out.println(upper.apply("java") + " " + (parse.apply("40") + 2) + " " + maker.get().append("ok")); } }
ana ben mei JAVA 42 ok
When a lambda just calls an existing method, a method reference is shorter: System.out::println instead of x -> System.out.println(x). Forms: Class::staticMethod, object::method, Class::instanceMethod (called on the argument) and Class::new.
Composing functions
import java.util.function.Function; import java.util.function.Predicate; public class Main { public static void main(String[] args) { Function<Double, Double> addVat = p -> p * 1.23; Function<Double, Double> discount = p -> p * 0.9; System.out.println("%.2f".formatted(discount.andThen(addVat).apply(100.0))); Predicate<String> notBlank = s -> !s.isBlank(); Predicate<String> shortText = s -> s.length() < 10; Predicate<String> valid = notBlank.and(shortText); System.out.println(valid.test("hello") + " " + valid.test(" ") + " " + valid.negate().test("a very long text")); } }
110.70 true false true
Your own functional interface
@FunctionalInterface interface PriceRule { double apply(double price); } public class Main { static double checkout(double price, PriceRule... rules) { for (PriceRule rule : rules) price = rule.apply(price); return price; } public static void main(String[] args) { PriceRule tenOff = p -> p - 10; PriceRule half = p -> p / 2; System.out.println(checkout(80, tenOff, half)); } }
35.0
@FunctionalInterface makes the compiler check that the interface has exactly one abstract method. Lambdas can capture local variables only if they're effectively final.
Try it yourself
Write a method that applies a Predicate to filter a list, and call it with different lambdas.
[8, 22, 50] [22, 41, 50] [Clara]