🎯 本章你会学到:用 Stream 把“过滤/映射/排序/统计”写成一条链,并完成词频 Top-N。建议边读边敲,每节练习先自己做,再展开答案对照。
Stream 是 Java 8 之后处理集合的推荐方式:把“对一堆数据做什么”写成一条链,读起来跟描述需求一样。写数据结构作业时未必用得上,但处理数据、写脚本时非常好用。
filter / map / sum / count
Stream 三件套:过滤、映射、排序
import java.util.List;
public class Main {
record Student(String name, int score) { }
public static void main(String[] args) {
List<Student> list = List.of(
new Student("Ann", 90),
new Student("Bob", 59),
new Student("Cid", 88));
long pass = list.stream()
.filter(s -> s.score() >= 60) // 过滤:及格
.count();
int total = list.stream()
.mapToInt(Student::score) // 映射成 int 流
.sum();
System.out.println("pass=" + pass);
System.out.printf("avg=%.1f%n", total / (double) list.size());
list.stream()
.sorted((a, b) -> Integer.compare(b.score(), a.score()))
.forEach(s -> System.out.println(s.name() + " " + s.score()));
}
}运行结果
pass=2
avg=79.0
Ann 90
Cid 88
Bob 59
groupingBy:一行做词频
词频 Top-N
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "apple", "cherry", "banana", "apple");
Map<String, Long> freq = words.stream()
.collect(Collectors.groupingBy(w -> w, Collectors.counting()));
freq.entrySet().stream()
.sorted((a, b) -> {
int c = Long.compare(b.getValue(), a.getValue()); // 次数降序
return c != 0 ? c : a.getKey().compareTo(b.getKey()); // 同次数按字典序
})
.limit(2)
.forEach(e -> System.out.println(e.getKey() + " " + e.getValue()));
}
}运行结果
apple 3
banana 2
| 操作 | 写法 | 说明 |
|---|---|---|
| 过滤 | .filter(x -> 条件) | 保留满足条件的元素 |
| 映射 | .map(x -> 新值) / .mapToInt(...) | 把元素变成另一种形式 |
| 排序 | .sorted(comparator) | 默认升序 |
| 统计 | .count() / .sum() / .average() | count 返回 long |
| 收集 | .collect(Collectors.toList()) | 把流变回集合 |
| 分组 | .collect(Collectors.groupingBy(f)) | 返回 Map |
⚠️ 易错点 Stream 是“一次性的”:同一个流只能消费一次,重复使用会抛
IllegalStateException。要复用就用集合本身。⚠️ 易错点 Stream 适合表达“做什么”,不适合塞复杂逻辑;有副作用(改外部变量、写文件)时用普通循环更清楚。
✍️ 本节练习
8.1必做用 Stream 统计
读入 n 个整数,输出其中 ≥ 60 的个数与总和(用 Stream 完成)。
输入 第一行 n;第二行 n 个整数。
输出 两行:pass=、sum=。
样例输入
5
90 59 88 60 30样例输出
pass=3
sum=238💡 提示 把数组转成流:Arrays.stream(a).filter(v -> v >= 60)。
✅ 查看参考答案与解析
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = sc.nextInt();
}
long pass = Arrays.stream(a).filter(v -> v >= 60).count();
int sum = Arrays.stream(a).filter(v -> v >= 60).sum();
System.out.println("pass=" + pass);
System.out.println("sum=" + sum);
}
}解析 数组没有 stream() 方法,要用 Arrays.stream(a);集合才有 .stream()。
8.2挑战成绩榜(综合练习)
读入若干行「姓名 分数」直到输入结束,输出人数、平均分(2 位小数)和按分数降序的前 3 名(格式 name score,用逗号分隔)。
输入 若干行「姓名 分数」,读到输入结束。
输出 三行:count=、avg=、top3=...。
样例输入
Ann 90
Bob 59
Cid 88
Dan 95
Eve 72样例输出
count=5
avg=80.80
top3=Dan 95,Ann 90,Cid 88💡 提示 用 List<Student> 收集后 stream().sorted(...),取前 3 个再 Collectors.joining(",")。
✅ 查看参考答案与解析
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;
public class Main {
record Student(String name, int score) { }
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
List<Student> list = new ArrayList<>();
while (sc.hasNext()) {
String name = sc.next();
if (!sc.hasNextInt()) {
break;
}
list.add(new Student(name, sc.nextInt()));
}
if (list.isEmpty()) {
return;
}
double avg = list.stream().mapToInt(Student::score).average().orElse(0);
String top3 = list.stream()
.sorted((a, b) -> Integer.compare(b.score(), a.score()))
.limit(3)
.map(s -> s.name() + " " + s.score())
.collect(Collectors.joining(","));
System.out.println("count=" + list.size());
System.out.printf("avg=%.2f%n", avg);
System.out.println("top3=" + top3);
}
}解析 average() 返回 OptionalDouble(可能没有元素),用 orElse(0) 兜底;Collectors.joining 负责拼接。这道题把集合、Stream、异常安全的输入处理串在一起。
📚 本文概念都在知识大全:
JVM 与字节码 · JDK · String 与 StringBuilder · 集合框架 · 泛型 · 受检异常 · 继承与接口 · record · Stream API