源本科技 | 码上会

Java Stream 流式处理

2026/01/29
76
0

学习目标

  • 理解 Java Stream 的核心概念与工作原理

  • 掌握 Stream 的创建方式与常用操作(中间操作与终端操作)

  • 能够区分顺序流、并行流与无限流的使用场景

  • 熟练运用 Stream 处理集合、文件及实际业务逻辑

  • 明确 Stream 与传统集合的本质区别


什么是 Java Stream

Java 8 引入的 Stream API 提供了一种函数式、声明式的方式来处理数据集合。它允许开发者以链式调用的方式对数据进行过滤、映射、排序、聚合等操作,而无需编写繁琐的循环逻辑。

重要特性:Stream 不存储数据,它只是对数据源(如 List、数组、文件等)进行“流水线式”的处理。

核心特点

  • 声明式编程:代码更简洁、意图更清晰

  • 惰性求值:中间操作不会立即执行,直到遇到终端操作才触发计算

  • 支持并行处理:通过 parallel()parallelStream() 利用多核 CPU

  • 不可变性:Stream 操作不会修改原始数据源

  • 一次性使用:一个 Stream 只能被消费一次,重复使用会抛出异常


Stream 工作流程

Stream 的处理过程分为三个阶段:

  1. 数据源:提供原始数据(如集合、数组、文件等)

  2. 中间操作:对数据进行转换、过滤等,返回新的 Stream(可链式调用)

  3. 终端操作:触发实际计算,产生最终结果(如列表、数值、输出等)


创建方式

1. 从集合创建

List<String> list = Arrays.asList("Java", "Python", "C++");
Stream<String> stream = list.stream();

2. 从数组创建

String[] arr = {"A", "B", "C"};
Stream<String> stream = Arrays.stream(arr);

3. 使用 Stream.of()

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);

4. 创建无限流

需配合 limit()

// 迭代生成:1, 2, 3, 4, 5
Stream<Integer> infinite = Stream.iterate(1, n -> n + 1).limit(5);

// 随机生成
Stream<Double> randoms = Stream.generate(Math::random).limit(3);

中间操作

中间操作返回一个新的 Stream,支持链式调用。常见操作包括:

操作

说明

filter(Predicate)

按条件筛选元素

map(Function)

将每个元素转换为另一种类型

sorted() / sorted(Comparator)

排序

distinct()

去重

skip(n)

跳过前 n 个元素

limit(n)

限制最多 n 个元素

链式处理

List<Integer> numbers = Arrays.asList(5, 10, 20, 10, 30, 40);

numbers.stream()
       .filter(n -> n > 10)    // 保留大于 10 的
       .map(n -> n * 2)        // 翻倍
       .distinct()             // 去重
       .sorted()               // 升序排序
       .forEach(System.out::println);

输出

40
60
80

终端操作

终端操作触发整个流水线的执行,并产生最终结果:

操作

说明

forEach(Consumer)

遍历每个元素

collect(Collector)

收集结果到集合(List/Set/Map)

reduce(BinaryOperator)

聚合为单个值(如求和、拼接)

count()

返回元素数量

anyMatch / allMatch / noneMatch

判断是否满足条件

findFirst() / findAny()

获取一个元素

多种终端操作

List<String> names = Arrays.asList("Amit", "Riya", "Rohan", "Amit");

// 收集为 Set(自动去重)
Set<String> unique = names.stream().collect(Collectors.toSet());

// 统计以 'R' 开头的名字数量
long count = names.stream().filter(n -> n.startsWith("R")).count();

// 拼接所有名字
String joined = names.stream().reduce("", (a, b) -> a + b + " ");

输出

[Amit, Riya, Rohan]
Names starting with R: 2
Amit Riya Rohan Amit 

Stream 的不同类型

1. 顺序流

默认的 Stream 类型,单线程依次处理元素。

list.stream().forEach(System.out::println);

2. 并行流

利用多线程并发处理,适合大数据量场景。

// 方式一:从集合创建
list.parallelStream().forEach(...);

// 方式二:转换已有流
stream.parallel().forEach(...);

注意:并行流不保证元素处理顺序,且可能因线程切换带来额外开销,小数据集慎用。

3. 无限流

通过 iterate()generate() 创建无界序列,必须用 limit() 截断。

Stream.iterate(0, n -> n + 2).limit(5) // 0, 2, 4, 6, 8

4. 原始类型流

避免装箱 / 拆箱开销,提升性能:

  • IntStream:处理 int

  • LongStream:处理 long

  • DoubleStream:处理 double

IntStream.range(1, 5).forEach(System.out::println); // 1, 2, 3, 4

Stream 与 Collection

特性

Collection

Stream

目的

存储数据

处理数据

数据存储

构建时机

立即创建

惰性求值

重复使用

可多次遍历

仅能消费一次

操作方式

命令式(循环)

声明式(函数式)

简单说:Collection 是“数据容器”,Stream 是“数据处理器”


在文件中的应用

1. 读取文件

按行处理

try (Stream<String> lines = Files.lines(Paths.get("data.txt"))) {
    List<String> upper5 = lines
        .filter(s -> s.length() == 5)
        .map(String::toUpperCase)
        .collect(Collectors.toList());
}

2. 写入文件

String[] words = {"Hello", "World", "Java"};

try (PrintWriter pw = new PrintWriter(Files.newBufferedWriter(Paths.get("output.txt")))) {
    Stream.of(words).forEach(pw::println);
}

优势:自动资源管理(try-with-resources)+ 函数式处理,代码更安全简洁


实战案例

你有一组学生对象,包含姓名、年龄和成绩。你想找出 成绩大于等于 80 分 的学生,按年龄升序排列,然后只提取他们的 姓名,最终得到一个姓名列表

import java.util.*;
import java.util.stream.Collectors;

class Student {
    private String name;
    private int age;
    private double score;

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    // Getters
    public String getName() { return name; }
    public int getAge() { return age; }
    public double getScore() { return score; }

    @Override
    public String toString() {
        return name + " (age: " + age + ", score: " + score + ")";
    }
}

public class StreamExample {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
            new Student("Alice", 20, 85.5),
            new Student("Bob", 19, 78.0),
            new Student("Charlie", 22, 90.0),
            new Student("Diana", 19, 82.5),
            new Student("Eve", 21, 75.0)
        );

        List<String> highScorersNames = students.stream()
            .filter(s -> s.getScore() >= 80)           // 筛选出成绩 >=80 的学生
            .sorted(Comparator.comparing(Student::getAge)) // 按年龄升序排序
            .map(Student::getName)                     // 提取姓名
            .collect(Collectors.toList());             // 收集为 List

        System.out.println(highScorersNames); 
        // 输出: [Diana, Alice, Charlie]
        // 解释: Bob 和 Eve 成绩 <80 被过滤;剩下三人中 Diana 和 Alice 都是19/20岁,Charlie 22岁
    }
}

此模式广泛应用于日志分析、报表生成、数据清洗等场景


重点总结

  • Stream 是处理集合数据的现代方式,强调“做什么”而非“怎么做”

  • 中间操作(filter/map/sorted)构建流水线,终端操作(collect/forEach)触发执行

  • 并行流可提升性能,但需权衡线程开销与数据规模

  • 原始类型流(IntStream 等)避免自动装箱,提升效率

  • Stream 适用于文件、数据库结果集、网络数据等多种数据源


思考题

  1. 为什么以下代码会抛出异常?如何修复?

    Stream<String> stream = list.stream();
    stream.forEach(System.out::println);
    stream.forEach(System.out::println); // ?
  2. 在什么情况下,使用 reducecollect 更合适?请举例说明。

  3. 尝试用 Stream 实现:从一个整数列表中找出所有偶数的平方和。