Java Lambda Expressions
A lambda expression is a short way to write a small function and pass it around like a value. Instead of writing a whole class to say "compare two players by score" or "keep only the even numbers", you write just the parameters, an arrow and the code: (a, b) -> a.score - b.score. Lambdas make code that works with collections much shorter, and they are the basis of Streams.
Syntax
A lambda implements an interface that has exactly one abstract method, called a functional interface. You met one in Inner Classes, where an anonymous class implemented Discount. The lambda does the same in one line:
interface Discount {
double apply(double price);
}
public class Main {
public static void main(String[] args) {
Discount festival = price -> price * 0.8;
Discount flat = price -> price - 100;
Discount none = price -> price;
System.out.println(festival.apply(1000));
System.out.println(flat.apply(1000));
System.out.println(none.apply(1000));
}
}800.0 900.0 1000.0
- One parameter needs no brackets:
price -> .... Zero or several parameters do:() -> ...,(a, b) -> .... - A single expression is returned automatically. For several statements, use curly brackets and
return.
Ready-made functional interfaces
You rarely need your own interface: java.util.function has the common shapes. Predicate answers yes or no, Function turns a value into another, Consumer does something with a value, and Supplier produces one:
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
Predicate<Integer> isEven = n -> n % 2 == 0;
Function<String, Integer> length = s -> s.length();
Supplier<String> greeting = () -> "Namaste";
System.out.println(isEven.test(10));
System.out.println(length.apply("lambda"));
System.out.println(greeting.get());
Predicate<Integer> isOddAndBig = isEven.negate().and(n -> n > 100);
System.out.println(isOddAndBig.test(101));
}
}true 6 Namaste true
Lambdas with collections
Many collection methods take a lambda. forEach runs it for every item, removeIf removes the items for which it returns true, and sort uses it to compare two items:
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> names = new ArrayList<>(List.of("Zoya", "Al", "Mohan", "Ira", "Bhavana"));
names.removeIf(n -> n.length() < 3);
names.sort((a, b) -> a.length() - b.length());
names.forEach(n -> System.out.println(n + " (" + n.length() + ")"));
}
}Ira (3) Zoya (4) Mohan (5) Bhavana (7)
Comparator and method references
The Comparator interface has helper methods that build comparisons from a lambda. When a lambda only calls an existing method, a method reference like String::length or System.out::println says the same thing even more shortly:
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
record Player(String name, int score) {
}
public class Main {
public static void main(String[] args) {
List<Player> players = new ArrayList<>(List.of(
new Player("Ana", 40), new Player("Raj", 75), new Player("Lee", 75), new Player("Kim", 60)));
players.sort(Comparator.comparing(Player::score).reversed().thenComparing(Player::name));
players.forEach(System.out::println);
}
}Player[name=Lee, score=75] Player[name=Raj, score=75] Player[name=Kim, score=60] Player[name=Ana, score=40]
The players are sorted by score from high to low, and players with the same score by name. Player is a record, so Player::score refers to its accessor method.
Using outside variables
A lambda can use variables from the surrounding method, but only if they are never changed afterwards (they are "effectively final"). This keeps lambdas predictable:
import java.util.List;
public class Main {
public static void main(String[] args) {
int limit = 50;
List<Integer> marks = List.of(35, 72, 48, 90);
marks.forEach(m -> System.out.println(m + (m >= limit ? " pass" : " fail")));
}
}35 fail 72 pass 48 fail 90 pass
Practice exercises
- Write a
Predicate<String>that is true for words starting with a capital letter and test it. - Sort a list of words alphabetically ignoring case with
String::compareToIgnoreCase. - Remove all negative numbers from a list with
removeIfand print the rest withforEach.