在这里插## 标题入图片描述

> 						大家好,我是程序员小羊!

✨博客主页: https://blog.csdn.net/m0_63815035?type=blog

💗《博客内容》:大数据、AI开发、Java、测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识
📢博客专栏: https://blog.csdn.net/m0_63815035/category_11954877.html
📢欢迎点赞 👍 收藏 ⭐留言 📝
📢本文为学习笔记资料,如有侵权,请联系我删除,疏漏之处还请指正🙉
📢大厦之成,非一木之材也;大海之阔,非一流之归也✨

在这里插入图片描述

超详细版

Stream API 是 Java 8 引入的一套函数式数据处理工具,它允许你以声明式方式处理集合、数组等数据源。本文从核心概念到具体操作,结合大量代码示例,帮你彻底掌握 Stream 的使用。


一、Stream 概述

1.1 什么是 Stream?

Stream 不是数据结构,它不存储数据,而是对数据源(集合、数组、I/O 资源等)的一种视图,支持链式操作。你可以把它想象成一条流水线:数据从源头进入,经过一系列中间处理(过滤、转换、排序),最后通过终端操作产出结果。

Stream 的设计借鉴了函数式编程语言(如 SQL 查询数据的方式)。例如:

SELECT name FROM emp WHERE salary > 5000 ORDER BY salary DESC

在这里插入图片描述

用 Stream 实现:

empList.stream()
    .filter(e -> e.getSalary() > 5000)
    .sorted(Comparator.comparingDouble(Employee::getSalary).reversed())
    .map(Employee::getName)
    .forEach(System.out::println);

1.2 Stream 的核心特点

  1. 不存储数据:Stream 只是数据源的一个视图,不改变原有数据结构。
  2. 函数式编程风格:对数据的操作以 Lambda 表达式传递,代码简洁。
  3. 惰性求值:中间操作(如 filtermap)不会立即执行,只有遇到终端操作(如 collectforEach)才会触发实际计算。
  4. 可并行:通过 parallelStream()parallel() 将顺序流转为并行流,利用多核提升性能。
  5. 不可重复使用:一个 Stream 只能被消费一次,终端操作后流即关闭。

1.3 Stream 的操作分类

类型 方法示例 特点
中间操作(Intermediate) filter, map, sorted, limit, distinct 返回 Stream,可链式调用,惰性执行
终端操作(Terminal) forEach, collect, reduce, count, min, max, anyMatch 触发计算,返回非 Stream 结果(或 void)
短路操作 limit, skip, anyMatch, findFirst 遇到足够结果即终止,可优化性能

二、Stream 的创建

2.1 从集合创建

集合接口(Collection)提供了 stream()parallelStream() 方法。

List<String> list = Arrays.asList("a", "b", "c");
Stream<String> sequentialStream = list.stream();          // 顺序流
Stream<String> parallelStream = list.parallelStream();    // 并行流

// 顺序流转并行
Stream<String> parallel = list.stream().parallel();

2.2 从数组创建

使用 Arrays.stream()Stream.of()

int[] intArray = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(intArray);   // 基本类型专用流

String[] strArray = {"a", "b", "c"};
Stream<String> strStream = Arrays.stream(strArray);
Stream<String> strStream2 = Stream.of("a", "b", "c");

2.3 使用 Stream.of() 直接创建

Stream<Integer> intStream = Stream.of(1, 2, 3, 4, 5);
Stream<String> strStream = Stream.of("Hello", "World");

2.4 使用 Stream.iterate() 和 Stream.generate()

这两个方法可以生成无限流,通常配合 limit() 使用。

// iterate:从种子开始,反复应用函数生成下一个元素
Stream.iterate(0, n -> n + 2)
    .limit(10)
    .forEach(System.out::println);   // 0,2,4,6,8,10,12,14,16,18

// generate:无限调用 Supplier 产生元素
Stream.generate(Math::random)
    .limit(5)
    .forEach(System.out::println);

Java 9 增加了 iterate 的重载,可带条件终止:

Stream.iterate(0, n -> n < 100, n -> n + 2)
    .forEach(System.out::println);

2.5 从文件行创建

try (Stream<String> lines = Files.lines(Paths.get("data.txt"), StandardCharsets.UTF_8)) {
    lines.filter(line -> line.contains("error"))
         .forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}

2.6 基本类型流(避免装箱)

Java 提供了 IntStream, LongStream, DoubleStream,用于处理基本类型,避免自动装箱带来的性能开销。

IntStream.range(1, 10).forEach(System.out::println);      // [1,10)
IntStream.rangeClosed(1, 10).forEach(System.out::println); // [1,10]

2.7 顺序流 vs 并行流

  • 顺序流:单线程处理,结果顺序稳定。
  • 并行流:内部使用 ForkJoinPool,将任务拆分到多个线程执行。适用于数据量大、元素处理无依赖的场景。注意并行流可能带来线程安全问题。
// 并行流计算
long sum = LongStream.rangeClosed(1, 10_000_000)
                     .parallel()
                     .sum();

三、Stream 中间操作详解

3.1 遍历/匹配(forEach, peek

  • forEach:终端操作,遍历每个元素。
  • peek:中间操作,用于调试或观察元素流过,不影响结果。
list.stream()
    .filter(x -> x > 5)
    .peek(System.out::println)   // 打印过滤后的元素
    .collect(Collectors.toList());

3.2 筛选(filter

接收 Predicate,过滤出符合条件的元素。

List<Employee> rich = employees.stream()
    .filter(e -> e.getSalary() > 10000)
    .collect(Collectors.toList());

3.3 映射(map, flatMap, mapToInt 等)

  • map:将每个元素转换成另一种形式。
  • flatMap:将每个元素转换为一个 Stream,然后将多个 Stream 扁平连接成一个 Stream。
// map:提取员工姓名
List<String> names = employees.stream()
    .map(Employee::getName)
    .collect(Collectors.toList());

// flatMap:将多个字符串按分隔符拆分,合并成单词流
String[] lines = {"Hello world", "Java Stream"};
List<String> words = Arrays.stream(lines)
    .flatMap(line -> Arrays.stream(line.split(" ")))
    .collect(Collectors.toList());   // ["Hello", "world", "Java", "Stream"]

基本类型转换 mapToInt, mapToLong, mapToDouble

IntStream salaryStream = employees.stream().mapToInt(Employee::getSalary);

3.4 排序(sorted

  • 无参:要求元素实现 Comparable
  • Comparator:自定义排序。
// 自然排序(Employee 需实现 Comparable)
employees.stream().sorted().collect(Collectors.toList());

// 按薪资倒序
employees.stream()
    .sorted(Comparator.comparingDouble(Employee::getSalary).reversed())
    .collect(Collectors.toList());

// 多级排序:先按部门,再按薪资
employees.stream()
    .sorted(Comparator.comparing(Employee::getDept)
            .thenComparingDouble(Employee::getSalary))
    .collect(Collectors.toList());

3.5 去重(distinct

根据元素的 equals()hashCode() 去除重复。

List<Integer> distinctNums = nums.stream().distinct().collect(Collectors.toList());

3.6 截断(limit, skip

  • limit(n):保留前 n 个元素。
  • skip(n):跳过前 n 个元素。
// 取前 5 个
Stream.iterate(1, i -> i + 1).limit(5).forEach(System.out::println); // 1,2,3,4,5

// 跳过前 2 个,再取 3 个
Stream.iterate(1, i -> i + 1).skip(2).limit(3).forEach(System.out::println); // 3,4,5

3.7 中间操作组合示例

List<String> result = employees.stream()
    .filter(e -> e.getSalary() > 5000)      // 过滤
    .map(Employee::getName)                 // 映射成姓名
    .distinct()                             // 去重
    .sorted()                               // 排序
    .limit(10)                              // 取前10
    .collect(Collectors.toList());

四、Stream 终端操作详解

4.1 遍历(forEach, forEachOrdered

// 顺序流,forEach 顺序不确定(并行流中顺序无保证)
list.parallelStream().forEach(System.out::println);

// 保证遇到顺序(并行时按源顺序处理,性能降低)
list.parallelStream().forEachOrdered(System.out::println);

4.2 匹配(anyMatch, allMatch, noneMatch

返回 boolean,短路操作。

boolean anyAbove10000 = employees.stream().anyMatch(e -> e.getSalary() > 10000);
boolean allAbove0 = employees.stream().allMatch(e -> e.getSalary() > 0);
boolean noneBelow0 = employees.stream().noneMatch(e -> e.getSalary() < 0);

4.3 查找(findFirst, findAny

返回 OptionalfindAny 在并行流中性能更好。

Optional<Employee> first = employees.stream().findFirst();
Optional<Employee> any = employees.parallelStream().findAny();

4.4 聚合(count, min, max

long count = employees.stream().count();

Optional<Employee> minSalary = employees.stream()
    .min(Comparator.comparingDouble(Employee::getSalary));

Optional<Employee> maxSalary = employees.stream()
    .max(Comparator.comparingDouble(Employee::getSalary));

4.5 归约(reduce

将流元素反复组合,得到一个值。

// 求和
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
Optional<Integer> sum = numbers.stream().reduce((a, b) -> a + b); // 10
Integer sumWithIdentity = numbers.stream().reduce(0, Integer::sum); // 10

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

// 字符串拼接
String concat = Stream.of("a", "b", "c").reduce("", (s1, s2) -> s1 + s2); // "abc"

reduce 的完整签名:

  • Optional<T> reduce(BinaryOperator<T> accumulator)
  • T reduce(T identity, BinaryOperator<T> accumulator)
  • <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)(用于并行流)

4.6 收集(collect

collect 是最灵活的终端操作,可以将流转换为集合、Map、字符串或执行自定义归约。Collectors 工具类提供了大量工厂方法。

4.6.1 归集(toList, toSet, toMap
List<String> nameList = employees.stream()
    .map(Employee::getName)
    .collect(Collectors.toList());

Set<Integer> deptSet = employees.stream()
    .map(Employee::getDeptId)
    .collect(Collectors.toSet());

Map<Integer, String> idToName = employees.stream()
    .collect(Collectors.toMap(Employee::getId, Employee::getName));

// toMap 处理 key 冲突
Map<Integer, Employee> idToEmp = employees.stream()
    .collect(Collectors.toMap(Employee::getId, Function.identity(), (oldVal, newVal) -> newVal));
4.6.2 统计(counting, summingInt, averagingDouble, summarizingInt
long count = employees.stream().collect(Collectors.counting());
double avgSalary = employees.stream().collect(Collectors.averagingDouble(Employee::getSalary));
int totalSalary = employees.stream().collect(Collectors.summingInt(Employee::getSalary));
DoubleSummaryStatistics stats = employees.stream()
    .collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(stats.getCount() + ", " + stats.getSum() + ", " + stats.getAverage());
4.6.3 分组(groupingBy
// 单级分组
Map<String, List<Employee>> byDept = employees.stream()
    .collect(Collectors.groupingBy(Employee::getDept));

// 分组并计数
Map<String, Long> countByDept = employees.stream()
    .collect(Collectors.groupingBy(Employee::getDept, Collectors.counting()));

// 多级分组:先按部门,再按薪资等级
Map<String, Map<String, List<Employee>>> multi = employees.stream()
    .collect(Collectors.groupingBy(Employee::getDept,
        Collectors.groupingBy(e -> e.getSalary() > 8000 ? "高薪" : "普通")));
4.6.4 分区(partitioningBy

分区是分组的一种特例,按 true/false 分成两个组。

Map<Boolean, List<Employee>> partitioned = employees.stream()
    .collect(Collectors.partitioningBy(e -> e.getSalary() > 10000));
4.6.5 连接(joining

仅用于字符串流。

String names = employees.stream()
    .map(Employee::getName)
    .collect(Collectors.joining(", ", "[", "]"));
4.6.6 归约收集(reducing

Collectors.reducing 提供了比 reduce 更灵活的归约方式,可与其他收集器组合。

// 所有员工薪资总和
Double total = employees.stream()
    .collect(Collectors.reducing(0.0, Employee::getSalary, Double::sum));

4.7 终端操作组合示例

// 统计每个部门平均薪资,并筛选出平均薪资 > 8000 的部门
Map<String, Double> avgSalaryByDept = employees.stream()
    .collect(Collectors.groupingBy(Employee::getDept,
        Collectors.averagingDouble(Employee::getSalary)))
    .entrySet().stream()
    .filter(e -> e.getValue() > 8000)
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

五、Optional 的使用

Stream 的 findFirst, findAny, min, max, reduce 等方法返回 Optional,用于安全地处理可能为 null 的情况。

Optional<Employee> opt = employees.stream().findFirst();
if (opt.isPresent()) {
    System.out.println(opt.get().getName());
}

opt.ifPresent(e -> System.out.println(e.getName()));  // 更优雅

String name = opt.map(Employee::getName).orElse("Unknown");

六、并行流注意事项

  1. 线程安全:避免在并行流中修改外部共享状态。例如 forEach 里不能直接 list.add
  2. 性能:数据量小或操作简单时,并行流可能比顺序流慢(线程调度开销)。
  3. 顺序依赖:需要保持顺序时使用 forEachOrdered 或避免并行。
  4. 正确使用收集器Collectors.toList 是线程安全的,但自定义收集器需注意。
  5. ForkJoinPool:默认使用公共池(ForkJoinPool.commonPool()),可通过系统属性 java.util.concurrent.ForkJoinPool.common.parallelism 调整并行度;也可以使用自定义池:
ForkJoinPool pool = new ForkJoinPool(4);
pool.submit(() -> largeList.parallelStream().forEach(...)).join();

七、数值流与装箱优化

当处理大量基本类型时,使用专用流(IntStream, LongStream, DoubleStream)避免装箱/拆箱开销。

// 使用 IntStream.range 生成数字
int sum = IntStream.rangeClosed(1, 100).sum();

// 从对象流转数值流
IntStream ageStream = employees.stream().mapToInt(Employee::getAge);

八、高级技巧与常见陷阱

8.1 惰性求值示例

Stream<String> stream = list.stream()
    .filter(s -> {
        System.out.println("filter: " + s);
        return s.length() > 3;
    });
// 此时没有输出,因为尚未执行终端操作
stream.forEach(System.out::println);
// 输出 filter 和打印结果

8.2 无限流应限制大小

Stream.generate(Math::random).limit(10).forEach(...);

8.3 避免在 forEach 中修改外部集合

// 错误示例
List<Integer> result = new ArrayList<>();
stream.forEach(result::add);   // 非线程安全,尤其在并行流中

// 正确做法
List<Integer> result = stream.collect(Collectors.toList());

8.4 重用 Stream 的误区

Stream<String> s = list.stream();
s.forEach(System.out::println);
s.forEach(System.out::println);  // 异常:stream has already been operated upon or closed

8.5 flatMapmap 的区别

  • map:一对一转换,输出流元素个数等于输入流。
  • flatMap:一对多转换,每个输入元素可能产生 0 个、1 个或多个输出元素,然后扁平化。
// flatMap 示例:将每个句子拆成单词
Stream<String> lines = Stream.of("Hello world", "Java stream");
Stream<String> words = lines.flatMap(line -> Arrays.stream(line.split(" ")));

九、完整示例

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

public class StreamDemo {
    static class Employee {
        int id;
        String name;
        String dept;
        double salary;
        // 构造器、getter、setter、toString 省略
    }

    public static void main(String[] args) {
        List<Employee> employees = generateEmployees();

        // 找出部门 "IT" 中薪资最高的前 3 名员工的姓名
        List<String> top3IT = employees.stream()
            .filter(e -> "IT".equals(e.getDept()))
            .sorted(Comparator.comparingDouble(Employee::getSalary).reversed())
            .limit(3)
            .map(Employee::getName)
            .collect(Collectors.toList());

        // 统计各部门平均薪资
        Map<String, Double> avgSalaryByDept = employees.stream()
            .collect(Collectors.groupingBy(Employee::getDept,
                Collectors.averagingDouble(Employee::getSalary)));

        // 总薪资
        double totalSalary = employees.stream()
            .mapToDouble(Employee::getSalary)
            .sum();
    }
}

十、结尾

Stream API 是 Java 函数式编程的核心,熟练掌握它可以让数据处理的代码更简洁、可读性更强,并利用并行计算提升性能。建议:

  1. 多使用 collect 替代显式循环。
  2. 优先使用方法引用(如 Employee::getSalary)保持代码简洁。
  3. 合理使用并行流(数据量大、无状态、不依赖顺序时)。
  4. 注意惰性求值的特点,避免忘记终端操作导致流不执行。
  5. 利用 Optional 安全处理可能为空的结果。

通过大量练习,你会发现 Stream 能解决日常开发中 80% 以上的集合处理问题。

今天这篇文章就到这里了,大厦之成,非一木之材也;大海之阔,非一流之归也。感谢大家观看本文

在这里插入图片描述

更多推荐