Java 8 Stream API 全面总结

1. Stream API 简介

Stream(流)是 Java 8 引入的一个重要特性,它提供了一种函数式编程的方式来处理集合数据。Stream API 允许以声明式方式处理数据集合,使代码更简洁、可读性更强,并且能够利用多核架构进行并行计算。

Stream 的特点:

  • 非存储的数据结构:Stream 不存储元素,而是按需计算
  • 函数式编程:强调无状态操作和不可变性
  • 惰性执行:中间操作不会立即执行,而是等到终端操作时才执行
  • 可能一次性使用:Stream 通常只能遍历一次
  • 内部迭代:Stream 操作内部完成迭代,而不是像集合那样需要显式迭代

2. 创建 Stream

从集合创建

List<String> list = Arrays.asList("张三", "李四", "王五");
Stream<String> stream = list.stream();
Stream<String> parallelStream = list.parallelStream(); // 并行流

从数组创建

String[] array = {"张三", "李四", "王五"};
Stream<String> stream = Arrays.stream(array);
Stream<String> partStream = Arrays.stream(array, 0, 2); // 部分数组

使用 Stream.of() 方法

Stream<String> stream = Stream.of("张三", "李四", "王五");

创建无限流

// 生成从0开始的无限整数流
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 1);
// 使用limit限制为前10个元素
infiniteStream.limit(10).forEach(System.out::println);

// 生成随机数流
Stream<Double> randomStream = Stream.generate(Math::random);
randomStream.limit(5).forEach(System.out::println);

3. 中间操作(Intermediate Operations)

中间操作返回一个新的 Stream,可以链式调用多个中间操作。中间操作是惰性执行的。

筛选和切片

// filter: 过滤元素
list.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);

// distinct: 去重
list.stream().distinct().forEach(System.out::println);

// limit: 限制数量
list.stream().limit(2).forEach(System.out::println);

// skip: 跳过元素
list.stream().skip(1).forEach(System.out::println);

映射

// map: 一对一映射转换
list.stream().map(String::length).forEach(System.out::println);

// flatMap: 一对多映射转换,常用于处理嵌套集合
List<List<Integer>> nestedList = Arrays.asList(
    Arrays.asList(1, 2), 
    Arrays.asList(3, 4)
);
nestedList.stream()
    .flatMap(Collection::stream)
    .forEach(System.out::println); // 输出 1, 2, 3, 4

排序

// 自然排序
list.stream().sorted().forEach(System.out::println);

// 自定义排序
list.stream().sorted(Comparator.comparing(String::length)).forEach(System.out::println);
list.stream().sorted(Comparator.comparing(String::length).reversed()).forEach(System.out::println);

窥视(不改变流)

// peek: 用于调试,不改变流
list.stream()
    .peek(e -> System.out.println("原始元素: " + e))
    .map(String::toUpperCase)
    .peek(e -> System.out.println("转换后: " + e))
    .forEach(System.out::println);

4. 终端操作(Terminal Operations)

终端操作触发流的执行并返回结果。执行终端操作后,流将被消费,不能再使用。

匹配和查找

// anyMatch: 至少一个元素匹配
boolean anyMatch = list.stream().anyMatch(s -> s.startsWith("张"));

// allMatch: 所有元素都匹配
boolean allMatch = list.stream().allMatch(s -> s.length() > 1);

// noneMatch: 没有元素匹配
boolean noneMatch = list.stream().noneMatch(s -> s.endsWith("六"));

// findFirst: 返回第一个元素
Optional<String> first = list.stream().findFirst();

// findAny: 返回任意元素(在并行流中很有用)
Optional<String> any = list.stream().findAny();

归约

// reduce: 将流中元素归约为一个值
Optional<String> reduced = list.stream().reduce((s1, s2) -> s1 + ", " + s2);
System.out.println(reduced.orElse(""));  // 输出: 张三, 李四, 王五

// 带初始值的reduce
String reducedWithIdentity = list.stream().reduce("前缀: ", (s1, s2) -> s1 + s2);
System.out.println(reducedWithIdentity);  // 输出: 前缀: 张三李四王五

// 计算数值和
int sum = Stream.of(1, 2, 3, 4, 5).reduce(0, Integer::sum);
System.out.println(sum);  // 输出: 15

收集

// collect: 将流转换为其他形式
List<String> collectedList = list.stream().collect(Collectors.toList());
Set<String> collectedSet = list.stream().collect(Collectors.toSet());
String joined = list.stream().collect(Collectors.joining(", "));
System.out.println(joined);  // 输出: 张三, 李四, 王五

// 分组
Map<Integer, List<String>> groupByLength = list.stream()
    .collect(Collectors.groupingBy(String::length));

// 分区
Map<Boolean, List<String>> partitioned = list.stream()
    .collect(Collectors.partitioningBy(s -> s.length() > 2));
    
// 统计
IntSummaryStatistics stats = list.stream()
    .collect(Collectors.summarizingInt(String::length));
System.out.println("平均长度: " + stats.getAverage());
System.out.println("最大长度: " + stats.getMax());

遍历

// forEach: 遍历每个元素
list.stream().forEach(System.out::println);

// forEachOrdered: 保证按顺序遍历(在并行流中很重要)
list.parallelStream().forEachOrdered(System.out::println);

计数

// count: 返回流中元素的个数
long count = list.stream().count();
System.out.println("元素个数: " + count);

5. 实用示例

示例1:过滤和转换员工数据

class Employee {
    private String name;
    private int age;
    private double salary;
    
    // 构造函数、getter和setter省略...
}

List<Employee> employees = Arrays.asList(
    new Employee("张三", 30, 10000),
    new Employee("李四", 25, 8000),
    new Employee("王五", 35, 12000),
    new Employee("赵六", 40, 15000)
);

// 获取所有年龄大于30的员工姓名,并按字母排序
List<String> names = employees.stream()
    .filter(e -> e.getAge() > 30)
    .map(Employee::getName)
    .sorted()
    .collect(Collectors.toList());
System.out.println(names);  // 输出: [王五, 赵六]

// 计算所有员工的平均薪资
double averageSalary = employees.stream()
    .mapToDouble(Employee::getSalary)
    .average()
    .orElse(0.0);
System.out.println("平均薪资: " + averageSalary);

示例2:处理文本

String text = "Stream API是Java 8引入的新特性,它提供了函数式编程的能力";

// 统计单词数(按空格分割)
long wordCount = Arrays.stream(text.split("\\s+")).count();
System.out.println("单词数: " + wordCount);

// 获取最长的单词
String longestWord = Arrays.stream(text.split("\\s+"))
    .max(Comparator.comparing(String::length))
    .orElse("");
System.out.println("最长的单词: " + longestWord);

示例3:数值流的特殊操作

// IntStream, LongStream, DoubleStream
IntStream intStream = IntStream.range(1, 10);  // 生成1到9的整数流
int sum = intStream.sum();
System.out.println("总和: " + sum);

// 统计
IntStream.of(1, 3, 5, 7, 9)
    .summaryStatistics()
    .forEach(stats -> {
        System.out.println("总和: " + stats.getSum());
        System.out.println("平均值: " + stats.getAverage());
        System.out.println("最大值: " + stats.getMax());
        System.out.println("最小值: " + stats.getMin());
        System.out.println("数量: " + stats.getCount());
    });

6. Stream API 最佳实践

  1. 优先使用流的方法而非迭代:流操作通常更简洁、可读性更高

  2. 合理使用并行流

    • 数据量大且每个元素处理开销大时使用并行流
    • 注意避免使用并行流处理有状态操作或者对顺序敏感的操作
    • 默认并行流使用 ForkJoinPool.commonPool(),可能影响系统其他部分
    // 适合并行的场景
    List<BigInteger> numbers = getVeryLargeList();
    numbers.parallelStream()
        .map(this::complexCalculation)
        .collect(Collectors.toList());
    
  3. 使用恰当的收集器:选择合适的 Collectors 方法可以大幅提高代码效率

  4. 避免过度使用流:简单任务可能使用传统循环更清晰

  5. 注意流的惰性执行:理解中间操作不会立即执行的特性

  6. 调试技巧:使用 peek() 方法可以在不改变流的情况下调试中间结果

  7. 避免副作用:保持流操作的纯函数特性,避免修改外部状态