什么是stream流

在 Java 中,Stream(流) 是 Java 8 引入的核心特性,本质是 对数据源(集合、数组等)进行高效操作的 "元素序列" ------ 它不存储数据,也不修改原数据源,而是通过一系列 "中间操作"(如过滤、排序、映射)和 "终端操作"(如收集、遍历、统计),以声明式(而非命令式)的方式处理数据,让代码更简洁、高效、易读。

一、先搞懂:Stream 不是什么?

  • 不是集合 / 数组:不存储元素,仅 "流经" 元素(类似水流,只传递数据,不保存);
  • 不修改原数据源:所有操作都返回新的 Stream 或最终结果,原始数据不变;
  • 不是迭代器:但支持类似迭代的 "遍历",且支持并行处理(无需手动写多线程);
  • 一次性使用:Stream 被终端操作消费后,就不能再重复使用(需重新创建)。

二、Stream 的核心特性

1. 声明式编程(关注 "做什么",而非 "怎么做")

命令式编程(Java 8 前):需手动写循环、判断逻辑(如筛选偶数);声明式编程(Stream):直接描述 "筛选偶数" 的目标,底层实现由 Stream 框架完成。

示例对比:

复制代码
// 命令式(手动循环筛选)
List<Integer> numbers = Arrays.asList(1,2,3,4,5);
List<Integer> evens = new ArrayList<>();
for (int num : numbers) {
    if (num % 2 == 0) {
        evens.add(num);
    }
}

// 声明式(Stream 筛选)
List<Integer> evens = numbers.stream()
                             .filter(num -> num % 2 == 0)
                             .collect(Collectors.toList());
2. 中间操作 + 终端操作(惰性执行)
  • 中间操作 :返回新的 Stream,不立即执行(如 filtersortedmaplimit);多个中间操作可 "链式调用",形成操作管道(Pipeline);
  • 终端操作 :触发实际计算,消费 Stream,返回非 Stream 结果(如 forEachcollectcountaverage);只有调用终端操作,中间操作才会一起执行(惰性执行,提升效率)。

示例:

复制代码
numbers.stream()
       .filter(num -> num % 2 == 0) // 中间操作(不执行)
       .sorted() // 中间操作(不执行)
       .forEach(System.out::println); // 终端操作(触发所有中间操作执行)
3. 支持并行处理(简单高效)

Stream 分为 "串行流" 和 "并行流":

  • 串行流:单线程处理(默认,stream() 生成);
  • 并行流:多线程处理(parallelStream() 生成或 parallel() 转换),无需手动管理线程。

示例(并行筛选偶数,自动多线程):

复制代码
List<Integer> evens = numbers.parallelStream()
                             .filter(num -> num % 2 == 0)
                             .collect(Collectors.toList());
4. 无状态 + 有状态操作
  • 无状态:每个元素的处理不依赖其他元素(如 filtermap),效率高;
  • 有状态:处理元素需依赖其他元素(如 sorted 需收集所有元素才能排序,limit 需计数),可能需要缓存。

三、Stream 的核心结构(3 步流程)

使用 Stream 通常分为 3 步:创建 Stream → 中间操作(链式)→ 终端操作(触发执行)

1. 第一步:创建 Stream(4 种常见方式)
  • 从集合创建(最常用):list.stream()(串行)、list.parallelStream()(并行);
  • 从数组创建:Arrays.stream(数组)Stream.of(数组元素)
  • 从值创建:Stream.of(1,2,3,4)
  • 生成无限流:Stream.generate(()->Math.random())(需用 limit 终止)。

示例:

复制代码
// 1. 集合创建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream();

// 2. 数组创建
String[] arr = {"x", "y", "z"};
Stream<String> stream2 = Arrays.stream(arr);

// 3. 直接创建
Stream<Integer> stream3 = Stream.of(10, 20, 30);

// 4. 无限流(生成 5 个随机数)
Stream<Double> randomStream = Stream.generate(Math::random).limit(5);
2. 第二步:中间操作(常用 API)

之前学过的 filtersortedlimit 都是中间操作,再补充几个核心:

  • map(Function):将元素映射为另一种类型(如整数转字符串、对象取属性);
  • distinct():去重(基于 equals() 方法);
  • skip(long n):跳过前 n 个元素。

示例(组合中间操作):

复制代码
List<User> users = Arrays.asList(
    new User("张三", 17),
    new User("李四", 22),
    new User("王五", 19),
    new User("李四", 22)
);

// 筛选成年用户 → 去重 → 提取姓名 → 跳过前 1 个 → 限制 1 个
users.stream()
     .filter(u -> u.getAge() >= 18) // 成年:李四、王五、李四
     .distinct() // 去重:李四、王五
     .map(User::getName) // 提取姓名:"李四"、"王五"
     .skip(1) // 跳过第一个:"王五"
     .limit(1) // 保留 1 个:"王五"
     .forEach(System.out::println); // 输出:王五
3. 第三步:终端操作(常用 API)
  • 遍历:forEach(Consumer)(无返回值,直接消费元素);
  • 收集:collect(Collector)(转为集合、Map 等,如 Collectors.toList());
  • 统计:count()(元素个数)、average()(平均值,仅数值流)、max(Comparator)(最大值);
  • 判断:anyMatch(Predicate)(是否存在符合条件的元素)、allMatch(Predicate)(是否所有元素符合)。

示例(终端操作):

复制代码
List<Integer> numbers = Arrays.asList(1,2,3,4,5,6);

// 1. 收集为集合
List<Integer> evenList = numbers.stream()
                                .filter(num -> num % 2 == 0)
                                .collect(Collectors.toList()); // [2,4,6]

// 2. 统计个数
long evenCount = numbers.stream()
                        .filter(num -> num % 2 == 0)
                        .count(); // 3

// 3. 判断是否存在大于 5 的元素
boolean hasGt5 = numbers.stream()
                        .anyMatch(num -> num > 5); // true

// 4. 求最大值
Optional<Integer> max = numbers.stream()
                               .max(Integer::compare); // 6

四、Stream 的核心价值

  1. 简化代码:用链式调用替代繁琐的循环、判断,代码行数大幅减少;
  2. 提高效率:惰性执行 + 并行处理,底层优化(如流水线操作、减少中间集合创建);
  3. 易维护:声明式编程让逻辑更清晰,可读性、可维护性提升;
  4. 功能强大:内置丰富的中间 / 终端操作,支持筛选、排序、映射、统计等常见场景。

五、关键注意事项

  1. 不修改原数据源:所有操作都是 "只读",原始集合 / 数组不会被改变;

  2. Stream 不可复用 :终端操作后 Stream 失效,需重新创建(如下方错误示例);

    复制代码
    Stream<Integer> stream = numbers.stream().filter(num -> num % 2 == 0);
    stream.forEach(System.out::println); // 终端操作,stream 失效
    stream.count(); // 报错:IllegalStateException(stream 已被消费)
  3. 并行流的线程安全 :并行处理时,若中间操作有状态(如自定义 map 中修改外部集合),需保证线程安全(推荐用无状态操作);

  4. 空指针处理 :Stream 不直接处理 null 元素,若数据源有 null,需提前用 filter(Objects::nonNull) 过滤。

总结

  • 是什么:Java 8 新增的 "元素序列",用于声明式处理数据源,不存储、不修改原数据;
  • 核心结构:创建 Stream → 中间操作(链式)→ 终端操作(触发执行);
  • 核心特性:惰性执行、并行支持、声明式编程、无状态 / 有状态操作;
  • 价值:简化代码、提高效率、易维护,是 Java 集合处理的 "升级方案";
  • 使用场景:集合筛选、排序、映射、统计等数据处理场景(替代传统循环)。
相关推荐
上78将1 小时前
JVM回收垃圾机制
java·开发语言·jvm
张人玉1 小时前
C#Https协议相关知识点
开发语言·https·c#
刘一说1 小时前
JDK 25新纪元:技术革新与老项目迁移的冷思考
java·开发语言
Byron Loong1 小时前
【C#】VS调试——带参调试
开发语言·c#
无限进步_1 小时前
C语言双向循环链表实现详解:哨兵位与循环结构
c语言·开发语言·数据结构·c++·后端·算法·链表
小帅学编程1 小时前
Java基础
java·开发语言
思密吗喽1 小时前
如何完全清除Node.js环境重装 Node.js彻底卸载指南
java·开发语言·node.js·毕业设计·课程设计
杨福瑞1 小时前
数据结构:栈
c语言·开发语言·数据结构
故事不长丨1 小时前
解锁C#编程秘籍:封装、继承、多态深度剖析
开发语言·数据库·c#