Chapter 4 · Robust, modern Java
Lambdas and streams
Step 1 of 22 · Concept
Code you can pass
Some methods need a small piece of code from you: "which of two names comes first?", "which items go?".
In this lesson you will:
- pass such code to a method as a lambda,
- sort, filter and print a list with lambdas,
- filter, change and add up a list in one chain: a stream.
Step 2 of 22 · Concept
sort needs a rule
list.sort(…) puts a list in order. It needs a rule that says which of two values comes first: a
Comparator. That is an interface, as in lesson 15, whose one method to write is compare(a, b).
A whole class for one rule is a lot of typing:
class ByLength implements Comparator<String> {
public int compare(String a, String b) {
return a.length() - b.length();
}
}
Step 3 of 22 · Concept
A lambda is a short method
A lambda writes the same rule in one line. It is the compare method, with no class and no name around it.
- javac knows from where the lambda goes, a
Comparator<String>, thataandbareStrings. - A negative value puts
afirst, a positive oneb, zero is a tie.
Step 4 of 22 · Concept
A lambda is an object
Comparator<String> byLength =
(a, b) -> a.length() - b.length();
names.sort(byLength);
Java turns the lambda into an object on the heap, here a Comparator<String>. A variable holds an arrow to it,
like any object.
sort gets a copy of that arrow. It calls the object's compare each time it compares two names.
Step 5 of 22 · Quiz
Predict the order
names holds Margaret, Ada, Grace, Alan and Bo. After these lines, what does
System.out.println(names) print?
Comparator<String> byLength =
(a, b) -> a.length() - b.length();
names.sort(byLength);
Step 6 of 22 · Demo
Sort by length
Run it and compare with your answer. List.of(…) makes a list that cannot change, so new ArrayList<>(…) copies
it into one that sort may change.
Step 7 of 22 · Try it
Try it: longest first
Change the lambda so that the longest name comes first. The program must print:
[Margaret, Grace, Alan, Ada, Bo]
Swap a and b after the arrow: when a is longer, the value must be negative.
Step 8 of 22 · Concept
Three methods that take a lambda
| method | calls its lambda | to |
|---|---|---|
list.sort(…) | for two elements at a time | put the list in order |
list.removeIf(…) | once per element | remove those that gave true |
list.forEach(…) | once per element, in order | do something with each, such as print it |
A lambda with one parameter needs no brackets: n -> n.length() < 4.
Step 9 of 22 · Demo
Sort, remove, print
One list, three lambdas. Run it.
Step 10 of 22 · Exercise
Your turn: best scores first
Each comment becomes one line with a lambda. Sort from highest to lowest, remove the scores under 50, and print the rest. The output must be:
Score: 90
Score: 72
Score: 66
Highest first is the step Try it: longest first again: the bigger value must give a negative number.
sort((a, b) -> b - a), then removeIf with s -> s < 50, then forEach that prints "Score: " + s.
scores.sort((a, b) -> b - a);
scores.removeIf(s -> s < 50);
Step 11 of 22 · Concept
A stream is a pipeline
words.stream() starts a stream: the list's elements flow through steps, one at a time.
filterkeeps the elements for which its lambda istrue.mapturns each element into a new value.toList()collects what is left into a new list, which cannot change.wordsdoes not change either.
Nothing flows until the last step asks. A stream runs once: call stream() again for a second pass.
Step 12 of 22 · Quiz
Predict the pipeline
What does this print?
List<String> words = List.of("apple", "kiwi", "banana",
"fig", "cherry");
System.out.println(words.stream()
.filter(w -> w.length() > 4)
.map(w -> w.toUpperCase())
.toList());
Step 13 of 22 · Demo
Filter, map, collect
Run it and compare with your answer. Read the chain from top to bottom; each step starts a line with its dot.
Step 14 of 22 · Concept
Add up, or join
Two more ways to end a stream:
| end | gives |
|---|---|
.mapToInt(w -> w.length()).sum() | each element's int, added up |
.collect(Collectors.joining(", ")) | one String, with ", " between the elements |
Collectors comes from java.util.stream, so it needs an import.
Step 15 of 22 · Demo
Three ways to end a stream
Three streams over the same words. Run it. The letters add up to 5 + 4 + 6 + 3 + 6, which is 24.
Step 16 of 22 · Exercise
Your turn: a shopping list
Item.java is a record with a name and a price. Write the bodies of cheap and total with streams.
cheapreturns the names of the items that cost less thanlimit, in the list's order.totaladds up every price.
The program must print:
[bread, apples, milk]
Total: 29
cheap is the chain of the step Filter, map, collect, on items instead of words.
cheap: filter with i -> i.price() < limit, then map with i -> i.name(), then toList(). total:
mapToInt(i -> i.price()).sum().
return items.stream()
.filter(i -> i.price() < limit)
.map(i -> i.name())
.toList();
Step 17 of 22 · Exercise
Your turn: initials
Write initials: the first letter of each name, joined by dots, with a stream that ends in
Collectors.joining. The program must print:
A.G.L
M.A
First turn each name into its first letter with map, then join them.
n.substring(0, 1) is the first letter of n (lesson 9). Collectors.joining(".") puts dots between.
return names.stream()
.map(n -> n.substring(0, 1))
Step 18 of 22 · Concept
When a loop is clearer
A stream is good at one job: take a list, keep some elements, change them, collect or add them up. For other
jobs, a plain for loop reads better.
| the job | write |
|---|---|
| keep some, change them, collect or add up | a stream |
| stop at the first match | a loop with break or return |
| update several variables in one pass | a loop |
use the index i, or print as you go | a loop |
Both run equally well. Choose what the next reader understands faster.
Step 19 of 22 · Quiz
Loop or stream?
Which job is clearer as a plain for loop?
Step 20 of 22 · Check yourself
Check yourself: a class report
Student.java is a record with a name and a score. Write the four methods, each as its comment says, so that
the program prints exactly:
Ada 92
Linus 75
Grace 58
Alan 40
Passed: [Ada, Linus]
Total: 265
Everyone: Ada, Linus, Grace, Alan
One sort with a lambda, then three streams: one ends in toList(), one in sum(), one in Collectors.joining.
Step 21 of 22 · Challenge · optional
Challenge: the top three
Optional. It earns the lesson's third star.
The stream prints every name. Make it keep only the three best students, highest score first. The program must print:
[Ada, Joan, Linus]
A stream can sort too: sorted(…) takes a Comparator, as list.sort does, and leaves the list itself alone.
limit(3) lets the first three elements through and drops the rest. Sort first, then limit, then map.
.sorted((a, b) -> b.score() - a.score())
.limit(3)
Step 22 of 22 · Recap
Recap
- A lambda,
(a, b) -> …, is a short method. Java makes it an object you can keep and pass. sort,removeIfandforEachcall the lambda you give them.- A stream sends a list's elements through steps:
filter,map, thentoList()orsum(). - When the job is not that shape, a plain loop is clearer.
Next: lesson 19 writes text into files and reads it back, so a program can keep what it made.
Review
3 questions from this lesson, in a new order each time. Answer from memory: 2 of 3 right on the first try passes. On a day after you finish the lesson, a pass earns 15 XP.
The review needs JavaScript.
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class ByLength {
public static void main(String[] args) {
List<String> names = new ArrayList<>(List.of(
"Margaret", "Ada", "Grace", "Alan", "Bo"));
Comparator<String> byLength =
(a, b) -> a.length() - b.length();
names.sort(byLength);
System.out.println(names);
}
}
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class ByLength {
public static void main(String[] args) {
List<String> names = new ArrayList<>(List.of(
"Margaret", "Ada", "Grace", "Alan", "Bo"));
Comparator<String> byLength =
(a, b) -> a.length() - b.length();
names.sort(byLength);
System.out.println(names);
}
}
import java.util.ArrayList;
import java.util.List;
public class Names {
public static void main(String[] args) {
List<String> names = new ArrayList<>(List.of(
"Margaret", "Ada", "Grace", "Alan", "Bo"));
names.sort((a, b) -> a.length() - b.length());
System.out.println(names);
names.removeIf(n -> n.length() < 4);
names.forEach(n -> System.out.println("Hello, " + n));
}
}
import java.util.ArrayList;
import java.util.List;
public class Scores {
public static void main(String[] args) {
List<Integer> scores = new ArrayList<>(
List.of(72, 45, 90, 38, 66));
// sort from highest to lowest
// remove the scores under 50
// print each score on a line of its own
}
}
import java.util.List;
public class Words {
public static void main(String[] args) {
List<String> words = List.of("apple", "kiwi", "banana",
"fig", "cherry");
System.out.println(words.stream()
.filter(w -> w.length() > 4)
.map(w -> w.toUpperCase())
.toList());
}
}
import java.util.List;
import java.util.stream.Collectors;
public class Fruit {
public static void main(String[] args) {
List<String> words = List.of("apple", "kiwi", "banana",
"fig", "cherry");
List<String> longOnes = words.stream()
.filter(w -> w.length() > 4)
.map(w -> w.toUpperCase())
.toList();
System.out.println(longOnes);
int letters = words.stream()
.mapToInt(w -> w.length())
.sum();
System.out.println("Letters: " + letters);
String shortOnes = words.stream()
.filter(w -> w.length() <= 4)
.collect(Collectors.joining(", "));
System.out.println("Short: " + shortOnes);
}
}
import java.util.List;
public class Shop {
static List<String> cheap(List<Item> items, int limit) {
return List.of();
}
static int total(List<Item> items) {
return 0;
}
public static void main(String[] args) {
List<Item> items = List.of(new Item("bread", 3),
new Item("cheese", 8), new Item("apples", 4),
new Item("coffee", 12), new Item("milk", 2));
System.out.println(cheap(items, 5));
System.out.println("Total: " + total(items));
}
}
import java.util.List;
import java.util.stream.Collectors;
public class Initials {
static String initials(List<String> names) {
return "";
}
public static void main(String[] args) {
List<String> a = List.of("Ada", "Grace", "Linus");
List<String> b = List.of("Margaret", "Alan");
System.out.println(initials(a));
System.out.println(initials(b));
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class Report {
// sorts the students, highest score first
static void sortByScore(List<Student> students) {
}
// names of the students who scored 60 or more
static List<String> passed(List<Student> students) {
return List.of();
}
// every score added up
static int total(List<Student> students) {
return 0;
}
// every name, separated by ", "
static String names(List<Student> students) {
return "";
}
public static void main(String[] args) {
List<Student> students = new ArrayList<>(List.of(
new Student("Grace", 58),
new Student("Ada", 92),
new Student("Alan", 40),
new Student("Linus", 75)));
sortByScore(students);
students.forEach(s ->
System.out.println(s.name() + " " + s.score()));
System.out.println("Passed: " + passed(students));
System.out.println("Total: " + total(students));
System.out.println("Everyone: " + names(students));
}
}
import java.util.List;
public class Top {
public static void main(String[] args) {
List<Student> students = List.of(
new Student("Grace", 58),
new Student("Ada", 92),
new Student("Alan", 40),
new Student("Linus", 75),
new Student("Joan", 81));
List<String> top = students.stream()
// keep the three best, highest first
.map(s -> s.name())
.toList();
System.out.println(top);
}
}