Lambda expressions
Sometimes a method needs to receive not data but an action: how to compare elements, what to remove, what to do with each one. A lambda expression is a short way to write such an action — a function without a name that can be stored in a variable or passed to a method.
The first lambda
A lambda consists of parameters, an arrow -> and a body. Here the lambda takes a number and returns its square, and the apply() method calls it:
void main() {
Function<Integer, Integer> square = x -> x * x;
IO.println(square.apply(5));
}Output:
25Function<Integer, Integer> is the variable's type: "a function that takes an Integer and returns an Integer". Such types are called functional interfaces; they are the subject of the next lesson.
Forms of writing
If there are no parameters or there are several, the parentheses are required. If the body has several lines, it goes in curly braces with an explicit return:
void main() {
Supplier<String> hello = () -> "Hello!";
BinaryOperator<Integer> add = (a, b) -> a + b;
Function<String, String> shout = text -> {
String upper = text.toUpperCase();
return upper + "!";
};
IO.println(hello.get());
IO.println(add.apply(2, 3));
IO.println(shout.apply("quiet"));
}Output:
Hello!
5
QUIET!Parameter types are usually not written — Java infers them from the context.
A lambda as an argument
Most often a lambda is passed straight to a method. You met this in the chapter on collections: removeIf() takes a condition, sort() a way to compare, forEach() an action:
void main() {
List<String> words = new ArrayList<>(List.of("cat", "dog", "elephant", "ox"));
words.removeIf(w -> w.length() < 3);
words.sort((a, b) -> b.length() - a.length());
words.forEach(w -> IO.println(w));
}Output:
elephant
cat
dogYour own method that takes a lambda
For a method to accept a lambda, the parameter must have a functional interface type. Then one and the same method does different work depending on the action passed in:
List<Integer> transform(List<Integer> numbers, Function<Integer, Integer> action) {
List<Integer> result = new ArrayList<>();
for (int n : numbers) {
result.add(action.apply(n));
}
return result;
}
void main() {
List<Integer> prices = List.of(10, 20, 30);
IO.println(transform(prices, p -> p * 2));
IO.println(transform(prices, p -> p - 5));
}Output:
[20, 40, 60]
[5, 15, 25]Capturing variables
A lambda can use variables from the surrounding method. This is called capturing. Such a variable must be effectively final — once assigned, it does not change:
void main() {
double rate = 1.2;
Function<Double, Double> withTax = price -> price * rate;
IO.println(withTax.apply(50.0));
}Output:
60.0Common mistake
You cannot change a captured local variable inside a lambda — for example, to count a sum with forEach():
void main() {
int total = 0;
List.of(1, 2, 3).forEach(n -> total += n);
}Output:
Main.java:3: error: local variables referenced from a lambda expression must be final or effectively final
List.of(1, 2, 3).forEach(n -> total += n);
^For counting, use an ordinary for-each loop or streams (stream), which are covered in the next chapter.