Streams of input
The streams so far start from a list, and input comes one line at a time. So a program that works on input with a stream first reads the lines into a list, with the loop you know from module 13, and then makes a stream of the list.
Read first, then stream
A weather station sends its temperature readings, in whole degrees, one per line. An empty line ends them:
import java.util.ArrayList;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
ArrayList<String> lines = new ArrayList<>();
while (true) {
String line = scanner.nextLine();
if (line.isEmpty()) {
break;
}
lines.add(line);
}
long belowZero = lines.stream()
.mapToInt(Integer::valueOf)
.filter(degrees -> degrees < 0)
.count();
long aboveZero = lines.stream()
.mapToInt(Integer::valueOf)
.filter(degrees -> degrees > 0)
.count();
System.out.println("Readings: " + lines.size());
System.out.println("Below zero: " + belowZero);
System.out.println("Above zero: " + aboveZero);
}
}
-3
2
5
-1
0
4
(an empty line)Readings: 6
Below zero: 2
Above zero: 3- The loop only reads and stores. It doesn't count or add anything: that's the streams' work.
mapToInt(Integer::valueOf)turns each line into anint:Integer::valueOfis step 2's method reference forline -> Integer.valueOf(line).- An IntStream has
filtertoo, andcount, so the filter can come aftermapToIntand compare plain numbers.
A list of numbers
When a program asks several questions about the same numbers, it can turn the lines into numbers once, with map(Integer::valueOf) and collect, and then stream the list of numbers as often as it likes. Before an average, it checks that there's at least one reading, since getAsDouble() crashes when there's no value (step 3):
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
ArrayList<String> lines = new ArrayList<>();
while (true) {
String line = scanner.nextLine();
if (line.isEmpty()) {
break;
}
lines.add(line);
}
List<Integer> readings = lines.stream()
.map(Integer::valueOf)
.collect(Collectors.toList());
if (readings.isEmpty()) {
System.out.println("No readings");
} else {
double average = readings.stream()
.mapToInt(degrees -> degrees)
.average()
.getAsDouble();
System.out.println("Average: " + average);
System.out.println("Frosty: " + readings.stream().filter(degrees -> degrees <= 0).count());
}
}
}
-3
2
5
-1
0
4
(an empty line)Average: 1.1666666666666667
Frosty: 3The average is 7 / 6, printed with every digit a double has, as in module 29.
Lines that aren't numbers
When the station misses a reading, it sends a - in its place. Integer.valueOf("-") crashes, so a filter must drop those lines, and it must come before the step that turns lines into numbers. A filter after mapToInt comes too late, whatever it keeps, because the crash comes first:
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> lines = new ArrayList<>();
lines.add("4");
lines.add("-");
lines.add("6");
int sum = lines.stream()
.mapToInt(Integer::valueOf)
.filter(degrees -> degrees > 0)
.sum();
System.out.println(sum);
}
}
Exception in thread "main" java.lang.NumberFormatException: For input string: "-"
at Main.main(Main.java:12)Java reports the line where the chain's terminal operation, sum(), runs, because that's when the lambdas and method references are called. The dash filter itself can't go after mapToInt at all: by then the values are ints, which have no equals. So filter the dashes out first, .filter(line -> !line.equals("-")), and then turn the rest into numbers: the sum is 10.