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/爬虫 实战干货。

更多推荐