Java Stream API 是 Java 8 引入的一个功能强大的工具,用于处理集合数据(如 List、Set 等)或数组的声明式操作。它提供了一种函数式编程风格的方式,让开发者能够以简洁、可读性高且高效的方式处理数据。Stream API 的核心思想是将数据操作分为一系列中间操作和终端操作,通过链式调用实现复杂的处理逻辑,同时支持并行处理以提高性能。

1. Stream API 的核心概念

Stream API 的设计灵感来源于函数式编程,强调数据的不可变性和声明式操作。以下是 Stream API 的核心概念:

  • Stream(流)

    • Stream 是一个抽象的数据处理管道,表示数据的序列,但并不存储数据本身。
    • Stream 可以看作是对数据源(如集合、数组)的“视图”,支持对其进行操作(如过滤、映射、排序等)。
    • Stream 不修改原始数据源,操作的结果通常是生成新的 Stream 或最终的结果。
  • 数据源

    • Stream 的数据源可以是集合(Collection,如 List、Set)、数组、文件、I/O 通道或其他数据结构。
    • 常见创建 Stream 的方式:
      List<String> list = Arrays.asList("apple", "banana", "cherry");
      Stream<String> stream = list.stream(); // 从 List 创建 Stream
      Stream<String> arrayStream = Stream.of("apple", "banana", "cherry"); // 从数组创建
  • 操作类型

    • 中间操作(Intermediate Operations):对 Stream 进行处理并返回一个新的 Stream,允许链式调用。中间操作是惰性求值的(lazy evaluation),只有在终端操作触发时才会执行。
    • 终端操作(Terminal Operations):触发 Stream 的处理并返回最终结果(如一个值、集合或无返回值)。终端操作会“关闭” Stream,之后 Stream 不可再用。
  • 并行处理

    • Stream API 支持并行处理,通过 parallelStream()parallel() 方法可以利用多核处理器并行执行操作,适合处理大数据量。

2. Stream API 的操作分类

Stream API 的操作分为两类:中间操作和终端操作。以下是常见的操作及其功能:

2.1 中间操作

中间操作是惰性的,只有当终端操作触发时才会执行。常见中间操作包括:

  • filter(Predicate):根据条件过滤 Stream 中的元素。

    List<String> list = Arrays.asList("apple", "banana", "cherry");
    list.stream()
        .filter(s -> s.startsWith("a"))
        .forEach(System.out::println); // 输出:apple
  • map(Function<T, R>):将 Stream 中的元素映射为另一种类型或格式。

    List<String> list = Arrays.asList("apple", "banana", "cherry");
    list.stream()
        .map(String::toUpperCase)
        .forEach(System.out::println); // 输出:APPLE, BANANA, CHERRY
  • flatMap(Function<T, Stream>):将 Stream 中的每个元素映射为一个新的 Stream,并将所有 Stream 展平为一个 Stream。

    List<List<String>> nestedList = Arrays.asList(
        Arrays.asList("a", "b"),
        Arrays.asList("c", "d")
    );
    nestedList.stream()
        .flatMap(Collection::stream)
        .forEach(System.out::println); // 输出:a, b, c, d
  • sorted() / sorted(Comparator):对 Stream 中的元素进行排序。

    List<String> list = Arrays.asList("banana", "apple", "cherry");
    list.stream()
        .sorted()
        .forEach(System.out::println); // 输出:apple, banana, cherry
  • distinct():去除 Stream 中的重复元素。

    List<String> list = Arrays.asList("apple", "banana", "apple");
    list.stream()
        .distinct()
        .forEach(System.out::println); // 输出:apple, banana
  • limit(long maxSize):限制 Stream 中元素的数量。

    List<String> list = Arrays.asList("apple", "banana", "cherry");
    list.stream()
        .limit(2)
        .forEach(System.out::println); // 输出:apple, banana
  • skip(long n):跳过 Stream 中的前 n 个元素。

    List<String> list = Arrays.asList("apple", "banana", "cherry");
    list.stream()
        .skip(1)
        .forEach(System.out::println); // 输出:banana, cherry
  • peek(Consumer):对 Stream 中的每个元素执行操作,通常用于调试。

    List<String> list = Arrays.asList("apple", "banana", "cherry");
    list.stream()
        .peek(s -> System.out.println("Processing: " + s))
        .filter(s -> s.startsWith("b"))
        .forEach(System.out::println);
    // 输出:
    // Processing: apple
    // Processing: banana
    // banana
    // Processing: cherry

2.2 终端操作

终端操作会触发 Stream 的处理并返回结果,常见终端操作包括:

  • forEach(Consumer):对 Stream 中的每个元素执行操作。

    List<String> list = Arrays.asList("apple", "banana", "cherry");
    list.stream().forEach(System.out::println);
  • collect(Collector):将 Stream 转换为集合或其他数据结构。

    List<String> list = Arrays.asList("apple", "banana", "cherry");
    List<String> result = list.stream()
        .filter(s -> s.length() > 5)
        .collect(Collectors.toList()); // 收集到 List
  • toList()(Java 16+):直接将 Stream 转换为 List。

    List<String> result = list.stream().toList();
  • reduce(T identity, BinaryOperator):将 Stream 中的元素归约为一个值。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
    int sum = numbers.stream()
        .reduce(0, Integer::sum); // 求和,结果为 10
  • count():返回 Stream 中元素的数量。

    long count = list.stream().filter(s -> s.startsWith("a")).count(); // 统计以 "a" 开头的元素
  • anyMatch(Predicate) / allMatch(Predicate) / noneMatch(Predicate)

    • 检查 Stream 中是否存在/全部满足/全部不满足某个条件的元素。
    boolean hasApple = list.stream().anyMatch(s -> s.equals("apple")); // true
  • findFirst() / findAny():查找 Stream 中的第一个元素或任意元素,返回 Optional。

    Optional<String> first = list.stream().findFirst(); // Optional[apple]

2.3 短路操作

某些操作具有“短路”特性,即一旦满足条件,Stream 就会停止处理:

  • findFirst()findAny():找到一个元素后立即结束。
  • anyMatch()allMatch()noneMatch():满足条件后立即返回结果。
  • limit():达到指定数量后停止处理。

3. Stream API 的特性

  • 惰性求值

    • 中间操作不会立即执行,只有当终端操作触发时,Stream 才会从头到尾处理数据。这种机制优化了性能,避免了不必要的计算。
    • 示例:
      list.stream()
          .filter(s -> {
              System.out.println("Filtering: " + s);
              return s.startsWith("a");
          })
          .forEach(System.out::println);
      // 只有调用 forEach 后,filter 才会执行
  • 不可变性

    • Stream 操作不会修改原始数据源,结果通常是新的 Stream 或集合。
    • 示例:
      List<String> list = Arrays.asList("apple", "banana");
      List<String> filtered = list.stream().filter(s -> s.startsWith("a")).toList();
      // list 保持不变,filtered 是新列表
  • 一次性使用

    • 一个 Stream 只能被消费一次,终端操作后 Stream 会关闭。如果需要再次使用,必须重新创建 Stream。
    • 示例:
      Stream<String> stream = list.stream();
      stream.forEach(System.out::println);
      // stream.forEach(System.out::println); // 抛出 IllegalStateException
  • 并行处理

    • 通过 parallelStream()parallel(),Stream 可以并行处理数据,适合多核处理器环境。
    • 示例:
      List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
      numbers.parallelStream()
             .map(n -> n * 2)
             .forEach(System.out::println); // 顺序不保证

4. Collectors 的使用

Collectors 类提供了丰富的工具,用于将 Stream 的结果收集到集合或其他数据结构中。常见用法包括:

  • 收集到集合

    List<String> list = Arrays.asList("apple", "banana", "cherry");
    List<String> result = list.stream().collect(Collectors.toList());
    Set<String> set = list.stream().collect(Collectors.toSet());
    Map<String, Integer> map = list.stream()
        .collect(Collectors.toMap(s -> s, String::length));
  • 分组

    Map<Integer, List<String>> byLength = list.stream()
        .collect(Collectors.groupingBy(String::length));
    // {5=[apple], 6=[banana, cherry]}
  • 分区

    Map<Boolean, List<String>> partitioned = list.stream()
        .collect(Collectors.partitioningBy(s -> s.length() > 5));
    // {false=[apple], true=[banana, cherry]}
  • 字符串拼接

    String joined = list.stream().collect(Collectors.joining(", ")); // apple, banana, cherry

5. 使用场景

Stream API 适用于以下场景:

  • 数据过滤和转换:从集合中筛选出符合条件的元素或进行格式转换。

    List<String> longNames = list.stream()
        .filter(s -> s.length() > 5)
        .map(String::toUpperCase)
        .toList();
  • 聚合计算:求和、平均值、最大值、最小值等。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
    double average = numbers.stream()
        .mapToInt(Integer::intValue)
        .average()
        .orElse(0.0);
  • 分组和统计:对数据进行分组或统计分析。

    Map<Integer, Long> countByLength = list.stream()
        .collect(Collectors.groupingBy(String::length, Collectors.counting()));
  • 并行处理:处理大数据量时利用多核处理器。

    List<Integer> largeList = // 大数据量
    largeList.parallelStream().map(n -> heavyComputation(n)).toList();

6. 注意事项

  1. 避免副作用

    • Stream 操作应尽量避免修改外部状态(如修改共享变量),否则可能导致线程安全问题。
    • 错误示例:
      List<String> result = new ArrayList<>();
      list.stream().forEach(s -> result.add(s)); // 副作用,不推荐
      推荐:
      List<String> result = list.stream().toList();
  2. 并行 Stream 的谨慎使用

    • 并行 Stream 并不总是更快,适合 CPU 密集型任务(如计算)而非 I/O 密集型任务。
    • 并行处理可能导致顺序不一致(如 forEach 的输出顺序)。
    • 示例:
      list.parallelStream().forEach(System.out::println); // 输出顺序不定
  3. 性能优化

    • 使用合适的操作顺序以减少计算量。例如,先 filtermap 可以减少处理的数据量。
      list.stream()
          .filter(s -> s.length() > 5) // 先过滤
          .map(String::toUpperCase)   // 再转换
          .toList();
  4. 异常处理

    • Stream 操作中的异常需要显式处理,通常结合 try-catch 或 Optional。
      list.stream()
          .map(s -> {
              try {
                  return someMethodThatMayThrow(s);
              } catch (Exception e) {
                  return null;
              }
          })
          .toList();
  5. 避免在 Stream 中使用循环

    • Stream API 的设计初衷是取代显式循环,尽量使用内置操作(如 mapfilter)而非嵌套循环。

7. 实际示例

以下是一个综合示例,展示 Stream API 在处理复杂数据时的能力:

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class StreamExample {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 25, "Female"),
            new Person("Bob", 30, "Male"),
            new Person("Charlie", 25, "Male"),
            new Person("Diana", 35, "Female")
        );

        // 按年龄分组,统计每组人数
        Map<Integer, Long> countByAge = people.stream()
            .collect(Collectors.groupingBy(Person::getAge, Collectors.counting()));
        System.out.println("Count by age: " + countByAge);

        // 筛选女性,转换为姓名大写,收集到 List
        List<String> femaleNames = people.stream()
            .filter(p -> p.getGender().equals("Female"))
            .map(p -> p.getName().toUpperCase())
            .toList();
        System.out.println("Female names: " + femaleNames);

        // 计算平均年龄
        double averageAge = people.stream()
            .mapToInt(Person::getAge)
            .average()
            .orElse(0.0);
        System.out.println("Average age: " + averageAge);
    }
}

class Person {
    private String name;
    private int age;
    private String gender;

    public Person(String name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public String getName() { return name; }
    public int getAge() { return age; }
    public String getGender() { return gender; }
}

输出:

Count by age: {25=2, 30=1, 35=1}
Female names: [ALICE, DIANA]
Average age: 28.75

8. 总结

Java Stream API 是一个功能强大且灵活的工具,适合处理集合数据的过滤、转换、聚合等操作。它通过声明式编程提高了代码的可读性和简洁性,同时支持并行处理以提升性能。开发者在使用 Stream API 时应注意避免副作用、合理使用并行处理、优化操作顺序,并结合 Collectors 实现复杂的数据处理需求。