Java course menu
Java Lambdas and Functional Interfaces: Predicate, Function, Consumer and Method References
Learn Java lambda expressions: syntax, functional interfaces, the built-in Predicate, Function, Consumer, Supplier and BiFunction, method references (Class::method), capturing effectively final variables, composing functions, and using lambdas with collections.
On this page
A lambda expression is a short, unnamed function you can pass around like a value. Lambdas (Java 8+) make code that sorts, filters, transforms or reacts to events much shorter than writing a whole class for each behaviour.
Syntax#
(parameters) -> expression
(parameters) -> { statements; return value; }import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> names = new ArrayList<>(List.of("Meera", "Asha", "Ravindra", "Jo"));
names.sort((a, b) -> a.length() - b.length()); // two parameters, one expression
System.out.println(names);
names.removeIf(name -> name.length() < 3); // one parameter: no parentheses needed
System.out.println(names);
names.forEach(name -> { // a block body
String upper = name.toUpperCase();
System.out.println(upper);
});
}
}[Jo, Asha, Meera, Ravindra]
[Asha, Meera, Ravindra]
ASHA
MEERA
RAVINDRAParameter types are usually inferred. Parentheses are optional for exactly one parameter, and an expression body needs no return.
Functional interfaces#
A lambda can be used wherever a functional interface is expected: an interface with exactly one abstract method. The lambda becomes the implementation of that method. Comparator (used by sort) and Predicate (used by removeIf) are functional interfaces.
java.util.function provides the common shapes, so you rarely need to define your own:
import java.util.function.*;
public class Main {
public static void main(String[] args) {
Predicate<Integer> isEven = n -> n % 2 == 0;
Function<String, Integer> length = s -> s.length();
Consumer<String> shout = s -> System.out.println(s.toUpperCase() + "!");
Supplier<String> greeting = () -> "Hello";
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
UnaryOperator<String> twice = s -> s + s;
System.out.println(isEven.test(4) + " " + isEven.test(7));
System.out.println(length.apply("lambda"));
shout.accept("hi");
System.out.println(greeting.get() + " " + add.apply(2, 3) + " " + twice.apply("ab"));
}
}true false
6
HI!
Hello 5 ababPassing behaviour into a method#
Accepting a functional interface as a parameter lets callers decide what the method does:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class Main {
static List<Integer> filter(List<Integer> items, Predicate<Integer> condition) {
List<Integer> result = new ArrayList<>();
for (int item : items) {
if (condition.test(item)) result.add(item);
}
return result;
}
public static void main(String[] args) {
List<Integer> nums = List.of(5, 12, 7, 20, 3, 15);
System.out.println(filter(nums, n -> n > 10));
System.out.println(filter(nums, n -> n % 5 == 0));
}
}[12, 20, 15]
[5, 20, 15]The Streams API is built on exactly this idea.
Method references#
When a lambda only calls an existing method, a method reference (::) is even shorter:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
Function<String, Integer> parse = Integer::parseInt;
List<String> words = new ArrayList<>(List.of("kiwi", "apple", "fig"));
System.out.println(parse.apply("42") + 1);
words.replaceAll(String::toUpperCase);
words.forEach(System.out::println);
}
}43
KIWI
APPLE
FIGCapturing variables: effectively final#
A lambda can use local variables from the surrounding method, but only if they are effectively final (never reassigned after initialization):
import java.util.List;
public class Main {
public static void main(String[] args) {
int limit = 10; // never changed: effectively final
List<Integer> nums = List.of(4, 11, 7, 20);
long above = nums.stream().filter(n -> n > limit).count();
System.out.println(above);
// limit = 20; // would make the lambda above fail to compile
}
}2To accumulate a result, do not modify a captured local variable; use a stream operation (sum, count, collect) or a field instead.
Composing functions#
Functional interfaces have default methods for combining them:
import java.util.function.Function;
import java.util.function.Predicate;
public class Main {
public static void main(String[] args) {
Predicate<String> notEmpty = s -> !s.isEmpty();
Predicate<String> shortWord = s -> s.length() < 5;
Predicate<String> valid = notEmpty.and(shortWord);
Function<Integer, Integer> doubleIt = x -> x * 2;
Function<Integer, Integer> addTen = x -> x + 10;
System.out.println(valid.test("kiwi") + " " + valid.test("") + " " + valid.test("banana"));
System.out.println(doubleIt.andThen(addTen).apply(5) + " " + doubleIt.compose(addTen).apply(5));
}
}true false false
20 30f.andThen(g) applies f then g; f.compose(g) applies g first. Predicates combine with and, or and negate.
Common mistakes#
Practice#
Practice exercise
Write a method applyAll(List<Integer> values, Function<Integer, Integer> f) that returns a new list with f applied to each value. Use it with a lambda that squares numbers and with the method reference Math::abs, on List.of(-3, 2, -1, 4).
Show solution
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class ApplyAll {
static List<Integer> applyAll(List<Integer> values, Function<Integer, Integer> f) {
List<Integer> result = new ArrayList<>();
for (int v : values) {
result.add(f.apply(v));
}
return result;
}
public static void main(String[] args) {
List<Integer> values = List.of(-3, 2, -1, 4);
System.out.println(applyAll(values, x -> x * x));
System.out.println(applyAll(values, Math::abs));
}
}[9, 4, 1, 16]
[3, 2, 1, 4]Key takeaway#
Finished this lesson?