Java中的Stream

Java中的Stream

1. 什么是 Stream?
核心思想: Stream(流)是一个来自数据源(如集合、数组、I/O channel)的元素队列,并支持聚合操作。

你可以把它想象成一个高级的迭代器(Iterator),但有两个根本性的区别:

  • 声明式编程: 你只需要指定"做什么",而不是"如何做"。(例如,"过滤出所有长度大于3的字符串"而不是"遍历每个元素,检查其长度,如果大于3则加入一个新列表")。
  • 可组合性: Stream 操作可以像流水线一样链接起来,形成一个复杂的处理流程,但只需要一次迭代。
  • 内部迭代: 迭代操作在 Stream 库内部自动完成,无需你手动写 for 循环。

1.1 核心特点详解:

  • 惰性求值(Lazy Evaluation):
    • 中间操作(如 filter、map)不会立即执行
    • 只有遇到终止操作(如 collect、forEach)时才会触发计算
    • 示例:list.stream().filter(x -> x > 10).count()中,filter 操作在 count 被调用时才执行
  • 不可复用(Single Use):
    • 每个 Stream 管道(pipeline)只能被消费一次
    • 重复使用会抛出 IllegalStateException
    • 解决方案:每次需要时重新创建 Stream
  • 内部迭代(Internal Iteration):
    • 不需要显式编写 for/while 循环
    • 迭代过程由 Stream API 内部处理
    • 对比:传统 for 循环是外部迭代
  • 函数式风格(Functional Style):
    • 支持 Lambda 表达式(如 x -> x*2)
    • 支持方法引用(如 String::length)
    • 无副作用:理想情况下不修改外部状态

1.2 Stream与集合的区别

特性 集合(Collection) Stream(流)
存储 存储实际数据 不存储数据
操作方式 外部迭代(foreach循环) 内部迭代
数据处理 立即执行 延迟执行
可重用性 可多次遍历 只能遍历一次
并行能力 需要手动实现 内置并行支持

2. Stream 操作的三个阶段
使用 Stream 通常涉及三个步骤,形成一个"流管道":

  • 创建流(Source): 从一个数据源(如集合)创建一个 Stream 对象。

  • 中间操作(Intermediate Operations): 对 Stream 进行一系列的处理/转换(如过滤、映射、排序),这些操作返回一个新的 Stream,所以可以链式调用。中间操作是"惰性的",它们不会立即执行,只是在遇到终端操作时构建一个处理流程。

  • 终端操作(Terminal Operation): 执行管道并产生结果。执行后,该流就被"消耗"了,不能再使用。结果可以是一个值(如 min, max, count),一个集合(如 collect),或者一个副作用(如 forEach)。


3. 核心操作详解
3.1 创建流

java 复制代码
// 1. 从集合创建 (最常用)
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream(); // 顺序流
Stream<String> stream2 = list.parallelStream(); // 并行流

// 2. 从数组创建
String[] array = {"a", "b", "c"};
Stream<String> stream3 = Arrays.stream(array);

// 3. 使用 Stream.of() 静态方法
Stream<String> stream4 = Stream.of("a", "b", "c");

// 4. 创建无限流 (常用于生成序列)
Stream<Integer> stream5 = Stream.iterate(0, n -> n + 2); // 0, 2, 4, 6...
Stream<Double> stream6 = Stream.generate(Math::random); // 无限个随机数

3.2 常见的中间操作

这些操作会返回一个新的 Stream,可以链式调用。

  • filter(Predicate): 过滤,保留满足条件的元素。
java 复制代码
list.stream().filter(s -> s.startsWith("A")); // 保留以"A"开头的字符串
  • map(Function<T, R>): 映射,将元素转换成另一种形式。
java 复制代码
list.stream().map(String::toUpperCase); // 将所有字符串转为大写
list.stream().map(s -> s.length()); // 将字符串流映射为它的长度流(Integer)
  • flatMap(Function<T, Stream>): 将每个元素转换成一个流,然后把所有流连接成一个流。用于"打平"嵌套结构。
java 复制代码
List<List<String>> nestedList = ...;
Stream<String> flatStream = nestedList.stream()
                                    .flatMap(List::stream); // 将多个List打平成单个元素流
  • distinct(): 去重。
  • sorted() / sorted(Comparator): 排序。
  • limit(long maxSize): 限制流的最大长度。
  • skip(long n): 跳过前 n 个元素。

3.3 常见的终端操作

这些操作会触发流的执行。

  • forEach(Consumer): 遍历每个元素。
java 复制代码
list.stream().forEach(System.out::println);
  • collect(Collector<T, A, R>): 最强大的终端操作,将流转换为其他形式,如 List, Set, Map,或进行复杂的汇总。
java 复制代码
List<String> newList = list.stream().filter(...).collect(Collectors.toList());
Set<String> set = list.stream().collect(Collectors.toSet());
String joined = list.stream().collect(Collectors.joining(", "));
Map<Integer, List<String>> groupByLength = list.stream().collect(Collectors.groupingBy(String::length));
  • toArray(): 将流转换为数组。

  • reduce(BinaryOperator): 将流中的元素反复结合,得到一个值。

java 复制代码
Optional<Integer> sum = Stream.of(1, 2, 3).reduce((a, b) -> a + b); // 6
  • min(Comparator) / max(Comparator): 获取最小/最大值。

  • count(): 返回流中元素的个数。

  • anyMatch(Predicate) / allMatch / noneMatch: 短路匹配,检查流中是否有元素匹配给定条件。

java 复制代码
boolean hasA = list.stream().anyMatch(s -> s.contains("A"));

4. 完整的例子

假设我们有一个 Person 对象的列表,我们想找出所有年龄大于18岁的人的名字,并按字母顺序排序,最后放入一个新的列表。

  • 传统方式(命令式):
java 复制代码
public static void main(String[] args) {
        List<Person> people = new ArrayList<>();
        List<String> adultNames = new ArrayList<>();
        for (Person person : people) {
            if (person.getAge() > 18) {
                adultNames.add(person.getName());
            }
        }
        Collections.sort(adultNames);
    }
  • Stream 方式(声明式):
java 复制代码
 public static void main(String[] args) {
        List<Person> people = new ArrayList<Person>();
        List<String> adultNames = people.stream()          // 1. 创建流
                .filter(person -> person.getAge() > 18)    // 2. 中间操作:过滤年龄
                .map(Person::getName)                      // 3. 中间操作:映射为名字
                .sorted()                                  // 4. 中间操作:排序
                .collect(Collectors.toList());             // 5. 终端操作:收集为List
    }

5. 重要特性与注意事项

  • 不存储数据: Stream 本身不存储数据,它只是数据源的视图。

  • 不修改源数据: Stream 操作不会修改底层数据源。例如,filter 不会从原集合中删除元素,它会生成一个不含这些元素的新 Stream。

  • 惰性执行(Laziness): 中间操作是惰性的,只有在终端操作被调用时,它们才会开始执行。这允许进行一些优化,比如短路操作(findFirst, anyMatch)。

  • 一次性使用: 一个 Stream 对象一旦被终端操作消耗,就不能再被使用。如果你需要再次遍历,必须创建一个新的 Stream。

  • 并行处理: 并行流(parallelStream)可以自动将工作分配到多个线程上,但并非总是更快。它适用于数据量大、处理耗时的场景,并且要确保操作是无状态和不干扰的。

相关推荐
yihuiComeOn38 分钟前
[源码系列:手写Spring] AOP第二节:JDK动态代理 - 当AOP遇见动态代理的浪漫邂逅
java·后端·spring
Porunarufu1 小时前
Java·关于List
java·开发语言
靠沿2 小时前
Java数据结构初阶——Collection、List的介绍与ArrayList
java·数据结构·list
程序猿小蒜2 小时前
基于springboot的的学生干部管理系统开发与设计
java·前端·spring boot·后端·spring
q***56382 小时前
Spring容器初始化扩展点:ApplicationContextInitializer
java·后端·spring
q***51892 小时前
SpringCloud系列教程:微服务的未来(十四)网关登录校验、自定义过滤器GlobalFilter、GatawayFilter
java·spring cloud·微服务
go__Ahead2 小时前
【Java】线程池源码解析
java·juc
wyhwust3 小时前
数组----插入一个数到有序数列中
java·数据结构·算法
专注于大数据技术栈3 小时前
java学习--final
java·开发语言·学习