Java8 Stream API 实战案例——让集合操作更优雅
·
Java8 引入的 Stream API 是 Java 发展史上最重要的更新之一。它让集合操作从"怎么做"变成了"做什么",代码更简洁、更易读。
一、Stream 是什么
传统方式操作集合需要写大量 for 循环和 if 判断:
// 传统方式:筛选出年龄大于18的用户名
List<String> names = new ArrayList<>();
for (User user : userList) {
if (user.getAge() > 18) {
names.add(user.getName());
}
}
用 Stream 一行搞定:
// Stream 方式
List<String> names = userList.stream()
.filter(u -> u.getAge() > 18)
.map(User::getName)
.collect(Collectors.toList());
二、创建 Stream
// 从集合创建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream();
// 从数组创建
String[] arr = {"a", "b", "c"};
Stream<String> stream2 = Arrays.stream(arr);
// 从值创建
Stream<String> stream3 = Stream.of("a", "b", "c");
三、常用中间操作
中间操作返回的是一个新的 Stream,可以链式调用。
1. filter——筛选
// 筛选成绩大于等于60分的学生
List<Student> passList = students.stream()
.filter(s -> s.getScore() >= 60)
.collect(Collectors.toList());
2. map——转换
// 提取所有学生姓名
List<String> names = students.stream()
.map(Student::getName)
.collect(Collectors.toList());
// 对象转字符串
List<String> infoList = students.stream()
.map(s -> s.getName() + " - " + s.getScore())
.collect(Collectors.toList());
3. distinct——去重
// 获取所有不重复的班级
List<String> classes = students.stream()
.map(Student::getClassName)
.distinct()
.collect(Collectors.toList());
4. sorted——排序
// 按成绩降序排序
List<Student> sorted = students.stream()
.sorted(Comparator.comparingInt(Student::getScore).reversed())
.collect(Collectors.toList());
// 先按班级、再按成绩排序
List<Student> sorted2 = students.stream()
.sorted(Comparator.comparing(Student::getClassName)
.thenComparingInt(Student::getScore))
.collect(Collectors.toList());
5. limit / skip——分页
// 跳过前10条,取10条(第二页)
List<Student> page2 = students.stream()
.skip(10)
.limit(10)
.collect(Collectors.toList());
四、常用终止操作
终止操作才是真正执行计算的时候。
1. forEach——遍历
students.stream()
.filter(s -> s.getScore() < 60)
.forEach(s -> System.out.println(s.getName() + "不及格"));
2. count——计数
long count = students.stream()
.filter(s -> s.getScore() >= 90)
.count();
System.out.println("优秀学生人数: " + count);
3. anyMatch / allMatch / noneMatch——匹配
// 是否有不及格的学生
boolean hasFail = students.stream()
.anyMatch(s -> s.getScore() < 60);
// 是否全部及格
boolean allPass = students.stream()
.allMatch(s -> s.getScore() >= 60);
// 是否没有不及格的
boolean noFail = students.stream()
.noneMatch(s -> s.getScore() < 60);
4. findFirst / findAny——查找
// 获取第一个成绩大于90的学生
Optional<Student> top = students.stream()
.filter(s -> s.getScore() > 90)
.findFirst();
top.ifPresent(s -> System.out.println(s.getName()));
五、collect——收集(最常用)
// 1. 转为 List
List<String> list = stream.collect(Collectors.toList());
// 2. 转为 Set(自动去重)
Set<String> set = stream.collect(Collectors.toSet());
// 3. 转为 Map
Map<String, Integer> map = students.stream()
.collect(Collectors.toMap(
Student::getName, // key
Student::getScore, // value
(a, b) -> a // key 冲突时保留第一个
));
// 4. 分组统计
Map<String, List<Student>> group = students.stream()
.collect(Collectors.groupingBy(Student::getClassName));
// 5. 分组后计数
Map<String, Long> countByClass = students.stream()
.collect(Collectors.groupingBy(Student::getClassName, Collectors.counting()));
// 6. 分组后求平均值
Map<String, Double> avgByClass = students.stream()
.collect(Collectors.groupingBy(
Student::getClassName,
Collectors.averagingInt(Student::getScore)
));
// 7. 拼接字符串
String names = students.stream()
.map(Student::getName)
.collect(Collectors.joining(", "));
// 输出: "张三, 李四, 王五"
六、实战:学生成绩统计
@Data
@AllArgsConstructor
class Student {
private String name;
private String className;
private int score;
}
public class StreamExample {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("张三", "大数据2301", 88),
new Student("李四", "大数据2301", 92),
new Student("王五", "大数据2302", 76),
new Student("赵六", "大数据2302", 45),
new Student("孙七", "软件2301", 63)
);
// 1. 各班级平均分
System.out.println("=== 各班平均分 ===");
students.stream()
.collect(Collectors.groupingBy(
Student::getClassName,
Collectors.averagingInt(Student::getScore)
)).forEach((c, avg) -> System.out.println(c + ": " + avg));
// 2. 不及格名单
System.out.println("\n=== 不及格学生 ===");
students.stream()
.filter(s -> s.getScore() < 60)
.forEach(s -> System.out.println(s.getName() + ": " + s.getScore()));
// 3. 各班最高分
System.out.println("\n=== 各班最高分 ===");
students.stream()
.collect(Collectors.groupingBy(
Student::getClassName,
Collectors.maxBy(Comparator.comparingInt(Student::getScore))
)).forEach((c, s) ->
System.out.println(c + ": " + s.get().getName() + "(" + s.get().getScore() + ")"));
// 4. 按成绩排名
System.out.println("\n=== 成绩排名 ===");
students.stream()
.sorted(Comparator.comparingInt(Student::getScore).reversed())
.forEach(s -> System.out.println(s.getName() + ": " + s.getScore()));
// 5. 成绩分布统计
System.out.println("\n=== 成绩分布 ===");
students.stream()
.collect(Collectors.groupingBy(s -> {
if (s.getScore() >= 90) return "优秀";
if (s.getScore() >= 80) return "良好";
if (s.getScore() >= 70) return "中等";
if (s.getScore() >= 60) return "及格";
return "不及格";
}, Collectors.counting()))
.forEach((level, count) -> System.out.println(level + ": " + count + "人"));
}
}
七、并行流——利用多核提升性能
数据量大时,可以用 parallelStream() 自动并行处理:
// 并行流(数据量小的时候不要用,反而更慢)
List<String> names = students.parallelStream()
.filter(s -> s.getScore() > 60)
.map(Student::getName)
.collect(Collectors.toList());
| 数据量 | 普通流 | 并行流 |
|---|---|---|
| 100条 | 0.1ms | 0.3ms |
| 1万条 | 5ms | 3ms |
| 100万条 | 500ms | 80ms |
建议: 数据量超过 10 万条再考虑并行流。
八、踩坑提醒
1. Stream 只能使用一次
Stream<String> stream = list.stream();
stream.forEach(System.out::println);
stream.filter(s -> s.startsWith("A")); // 报错!stream 已关闭
2. 不要在 forEach 中修改外部变量
// 错误
List<String> result = new ArrayList<>();
list.stream().forEach(s -> result.add(s)); // 线程不安全
// 正确
List<String> result = list.stream().collect(Collectors.toList());
3. Optional 判空
// 正确用法
Optional<Student> opt = students.stream()
.filter(s -> s.getScore() > 90)
.findFirst();
opt.ifPresent(s -> System.out.println(s.getName()));
// 不要直接用 get()
Student s = opt.get(); // 没有值时会抛异常
总结
Stream API 是现代 Java 开发的必备技能。掌握它之后,你会发现自己写 for 循环的次数越来越少。记住一个原则:
传统 for 循环 = 告诉计算机怎么做 → Stream = 告诉计算机要什么
如果对你有帮助,欢迎点赞、评论、关注【张老师技术栈】,持续分享 Java/Python/爬虫 实战干货。
更多推荐
所有评论(0)