Stream 是 Java 8 引入的一种新的抽象,用于处理集合类 (Collection) 的数据。Stream 并不存储数据,而是按需计算数据。Stream 操作有两个重要特性:
  1. 流水线操作 (Pipelining):Stream 操作可以链式调用,形成一个流水线,这些操作既可以是中间操作(intermediate operation),也可以是终端操作(terminal operation)。
  2. 内部迭代 (Internal Iteration):不同于集合类的外部迭代 (external iteration),Stream 使用内部迭代,通过底层的迭代器实现。

Stream 的优点

  1. 简洁性:Stream API 提供了声明性的方法链来处理数据,这使得代码更简洁、更易读。
  2. 易于并行化:Stream API 提供了简单的并行化处理数据的方式,通过 parallelStream 可以轻松实现并行计算,提高处理性能。
  3. 函数式编程风格:Stream API 支持函数式编程,允许使用 Lambda 表达式和方法引用,减少了样板代码。
  4. 延迟执行:中间操作是惰性求值的,只有在终端操作执行时才会计算,优化了性能。
  5. 更高的抽象层次:Stream API 提供了一种更高层次的数据处理抽象,使得代码更具表达力和可维护性。

用途

Stream API 主要用于对集合数据进行操作,比如过滤、排序、映射、归约等。它提供了一种函数式编程的风格,使代码更简洁、易读、可维护。常见用途包括:

  • 过滤 (Filtering):从集合中筛选出符合条件的元素。
  • 映射 (Mapping):将集合中的每个元素映射成另一种形式。
  • 归约 (Reduction):将集合中的元素组合成一个值。
  • 收集 (Collecting):将处理后的数据转换为其他集合形式。
  • 统计 (Statistics):对数据进行统计计算,如计数、求和、平均值等。

常用的 Stream 操作示例:

1. 创建 Stream
List<String> list = Arrays.asList("a", "b", "c", "d");

// 从集合创建 Stream
Stream<String> stream = list.stream();

// 从数组创建 Stream
Stream<String> stream = Stream.of("a", "b", "c", "d");
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
2. 中间操作 (Intermediate Operations)

中间操作会返回一个新的 Stream,它们是惰性求值的,只有在终端操作执行时才会执行。

// 过滤
Stream<String> filteredStream = stream.filter(s -> s.startsWith("a"));

// 映射
Stream<String> mappedStream = stream.map(String::toUpperCase);

// 排序
Stream<String> sortedStream = stream.sorted();

// 去重
Stream<String> distinctStream = stream.distinct();

 // 排序
Stream<String> sortedStream = stream.sorted();
Stream<String> sortedByComparator = stream.sorted((s1, s2) -> s2.compareTo(s1));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
3. 终端操作 (Terminal Operations)

终端操作会触发 Stream 的计算,并生成一个结果或副作用。

// 收集
List<String> collectedList = stream.collect(Collectors.toList());

// 统计
long count = stream.count();

// 查找
Optional<String> firstElement = stream.findFirst();

//reduce:归约操作,将元素组合成一个值
Optional<String> concatenated = stream.reduce((s1, s2) -> s1 + s2);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

代码案例

案例 1:过滤和映射
List<String> strings = Arrays.asList("apple", "banana", "orange", "apple", "mango");

List<String> result = strings.stream()        //获取stream对象
        .filter(s -> s.startsWith("a"))       //过滤,检查列表中是否以“a”开头,满足条件保留到流中
        .map(String::toUpperCase)             //映射,将流中每个字符串转换为大写
        .collect(Collectors.toList());        //收集,将流中的元素收集到一个list中

System.out.println(result);  // 输出:[APPLE, APPLE]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
案例 2:排序和去重
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);

List<Integer> result = numbers.stream()
        .distinct()                            //去重
        .sorted()                              //排序
        .collect(Collectors.toList());

System.out.println(result);  // 输出:[2, 3, 5, 7]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
案例 3:归约操作
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

int sum = numbers.stream()
        .reduce(0, Integer::sum);   //reduce:归约操作,将元素组合成一个值

System.out.println(sum);  // 输出:15
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
案例 4:分组操作
List<String> strings = Arrays.asList("apple", "banana", "orange", "apple", "mango");

Map<String, Long> result = strings.stream()
        .collect(Collectors.groupingBy(s -> s, Collectors.counting()));

    //终端操作,收集 使用Collectors.groupingBy()方法来收集流中的元素。
    // 这个方法接受两个参数:一个是分类函数(在这里是s -> s,表示使用字符串本身作为键)
    // 另一个是下游的收集器(在这里是Collectors.counting(),用于计算每个键的出现次数)。

System.out.println(result);  // 输出:{orange=1, banana=1, apple=2, mango=1}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
案例 5:并行流
List<String> strings = Arrays.asList("apple", "banana", "orange", "apple", "mango");

List<String> result = strings.parallelStream()   //并行处理集合中的数据,适用与大规模的计算、数据转换、过滤等操作
        .filter(s -> s.startsWith("a"))
        .map(String::toUpperCase)
        .sorted()
        .collect(Collectors.toList());

System.out.println(result);  // 输出:[APPLE, APPLE]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
案例 6:生成数列
List<Integer> numbers = IntStream.range(1, 10)   //创建了一个从1到9的整数序列的IntStream
        .boxed()    //将IntStream中的原始类型int转换成Integer对象的Stream
        .collect(Collectors.toList());

System.out.println(numbers);  // 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

案例 7:字符串拼接

List<String> strings = Arrays.asList("apple", "banana", "orange");

String result = strings.stream()
        .collect(Collectors.joining(", "));   //字符串拼接

System.out.println(result);  // 输出:apple, banana, orange
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

注意事项

1.排序 stream.sorted()
Stream<String> sortedStream = stream.sorted();
Stream<String> sortedByComparator = stream.sorted((s1, s2) -> s2.compareTo(s1));
  • 1.
  • 2.
  1. 无参的 sorted():元素按照它们的自然顺序排序。如果你的元素是数字或字符串等基本类型,它们将按照自然数值或字典顺序排序。如果你的元素是对象,它们将按照 Comparable 接口中定义的顺序排序。
  2. 带Comparator的 sorted():元素按照你提供的 Comparator 排序。这允许你定制排序逻辑,例如,你可以按字符串长度排序、按某个属性的逆序排序等。
2.中间操作 (Intermediate Operations) 和 终端操作 (Terminal Operations) 的区别
  • 中间操作:
  • 返回一个新的 Stream。
  • 惰性求值:中间操作本身不会触发实际的计算,只有在终端操作执行时才会计算。
  • 例子:filter, map, flatMap, distinct, sorted, limit, skip, peek。
  • 终端操作:
  • 触发 Stream 的计算,并生成结果。
  • 不是惰性求值,一旦调用终端操作,整个 Stream 的计算就会执行。
  • 终端操作会关闭 Stream,之后不能再对其进行操作。
  • 例子:forEach, toArray, reduce, collect, min, max, count, anyMatch, allMatch, noneMatch, findFirst, findAny。