What is a Lambda Expression?#
Lambda expressions are a core feature introduced in Java 8, enabling more concise code. Here is the most straightforward example:
1
2
3
4
5
6
7
8
9
10
| Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("runnable1 start!!!");
}
};
Runnable runnable2 = () -> System.out.println("runnable2 start!!!");
runnable1.run();
runnable2.run();
|
Both blocks are equivalent, but the lambda version is a single line. The basic form is () -> expression or () -> { statements; }.
With Parameters, No Return Value#
1
2
3
4
5
6
7
8
9
| JButton testButton = new JButton("Test Button");
testButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
System.out.println("Click Detected by Anon Class");
}
});
testButton.addActionListener(e -> System.out
.println("Click Detected by Lambda Listner"));
|
Parentheses can be omitted for single parameters: e -> expression.
With Parameters and Return Value#
1
2
3
4
5
6
7
8
9
10
11
12
| List<Person> personList = Person.createShortList();
// Anonymous inner class
Collections.sort(personList, new Comparator<Person>() {
public int compare(Person p1, Person p2) {
return p1.getSurName().compareTo(p2.getSurName());
}
});
// Lambda version
Collections.sort(personList,
(Person p1, Person p2) -> p1.getSurName().compareTo(p2.getSurName()));
|
For multiple parameters with a return value: (p1, p2) -> { return expression; }. Parameter types can be omitted when inferable.
@FunctionalInterface#
An interface with a single abstract method is treated as a functional interface by Java 8, even without the @FunctionalInterface annotation. Adding the annotation makes the intent explicit and triggers a compiler error if the contract is violated.
Common functional interfaces include Runnable, Comparator, and ActionListener.
Built-in Functional Interfaces#
Java 8 provides standard functional interfaces in java.util.function:
| Interface | Description |
|---|
Predicate<T> | Takes T, returns boolean |
Consumer<T> | Takes T, returns void |
Function<T, R> | Takes T, returns R |
Supplier<T> | Takes nothing, returns T (factory pattern) |
UnaryOperator<T> | Takes T, returns T (unary operation) |
BinaryOperator<T> | Takes two T, returns T (binary operation) |
Stream API and Collection Operations#
Combined with the Stream API, lambdas enable highly readable data processing:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| List<Person> pl = Person.createShortList();
// forEach
pl.forEach(p -> p.printWesternName());
pl.forEach(Person::printEasternName);
pl.forEach(p -> {
System.out.println(p.printCustom(
r -> "Name: " + r.getGivenName()));
});
// filter + forEach
pl.stream().filter(p -> p.getAge() > 16)
.forEach(Person::printWesternName);
// filter + collect (create new list)
Predicate<Person> allDraftees =
p -> p.getAge() >= 18 && p.getAge() <= 25
&& p.getGender() == Gender.MALE;
List<Person> pilotList = pl
.stream()
.filter(allDraftees)
.collect(Collectors.toList());
// mapToInt + sum
Predicate<Person> allPilots =
p -> p.getAge() >= 23 && p.getAge() <= 65;
long totalAge = pl
.stream()
.filter(allPilots)
.mapToInt(p -> p.getAge())
.sum();
// parallelStream + average
OptionalDouble averageAge = pl
.parallelStream()
.filter(allPilots)
.mapToDouble(p -> p.getAge())
.average();
|
Generic Processing Pipeline#
The following example combines Predicate, Function, and Consumer into a generic processing pipeline. Readability decreases with nesting, but understanding each interface’s role makes the pattern clear:
1
2
3
4
5
6
7
8
9
10
11
12
| public static <X, Y> void processElements(
Iterable<X> source,
Predicate<X> tester,
Function<X, Y> mapper,
Consumer<Y> block) {
for (X p : source) {
if (tester.test(p)) {
Y data = mapper.apply(p);
block.accept(data);
}
}
}
|
References#