【集合】Java 8 - Stream API 17种常用操作与案例详解

发布于:2024-12-23 ⋅ 阅读:(18) ⋅ 点赞:(0)

Java8 Stream API 17种常用操作与案例详解

在 Java 8 中,Stream API 提供了一种高效、简洁的数据处理方式,特别适合对集合、数组等数据源进行操作。Stream 通过函数式编程风格,支持链式调用,避免了传统 for 循环中复杂的代码逻辑。本文将详细介绍 17 种常用 Stream 操作方法,并通过示例代码帮助大家理解如何应用于实际开发中。


1. collect():将流中的元素收集到集合中

collect() 方法用于将流中的数据收集到集合、映射等数据结构中,非常常见。

示例

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

// 收集名字长度大于 3 的元素到 List 集合
List<String> filteredNames = names.stream()
                                  .filter(name -> name.length() > 3)
                                  .collect(Collectors.toList());
System.out.println(filteredNames); // Output: [Alice, Charlie, David]

理解要点
collect() 是终止操作,它将流中的元素重新汇总到新的集合中。这里的 Collectors.toList() 是一种常见用法,用于收集元素到 List 集合中。


2. filter():根据条件过滤流中的元素

filter() 用于筛选出满足特定条件的元素,它返回一个新的流,流中的元素都满足过滤条件。

示例

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 过滤出偶数
List<Integer> evenNumbers = numbers.stream()
                                   .filter(n -> n % 2 == 0)
                                   .collect(Collectors.toList());
System.out.println(evenNumbers); // Output: [2, 4]

理解要点
这里 n -> n % 2 == 0 是一个 Lambda 表达式,表示只保留能被 2 整除的元素。filter() 不会修改原数据,而是返回一个新的流。


3. map():元素映射为另一个值

map() 会对流中的每个元素执行操作,并将结果映射成一个新的流。

示例

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 转换为大写
List<String> upperCaseNames = names.stream()
                                   .map(String::toUpperCase)
                                   .collect(Collectors.toList());
System.out.println(upperCaseNames); // Output: [ALICE, BOB, CHARLIE]

理解要点

  • map() 接受一个函数作为参数,将流中的每个元素转换为新形式。
  • String::toUpperCase 是方法引用,等同于 name -> name.toUpperCase()

4. forEach():对流中的元素执行操作

forEach() 是终止操作,用于遍历流中的元素并执行指定的操作。

示例

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 输出每个元素
numbers.stream().forEach(System.out::println);
// Output: 1 2 3 4 5

理解要点

  • System.out::println 是方法引用,等同于 n -> System.out.println(n)
  • forEach() 不返回新流,只执行操作。

5. flatMap():将流中的元素展开成一个流

flatMap() 用于处理嵌套集合(比如 List),它将每个元素的流展开,并合并成一个新的流。

示例

List<List<Integer>> listOfLists = Arrays.asList(
    Arrays.asList(1, 2, 3), 
    Arrays.asList(4, 5), 
    Arrays.asList(6, 7, 8)
);

// 合并多个列表为一个流
List<Integer> mergedList = listOfLists.stream()
                                      .flatMap(List::stream)
                                      .collect(Collectors.toList());
System.out.println(mergedList); // Output: [1, 2, 3, 4, 5, 6, 7, 8]

理解要点

  • flatMap() 的作用是 “摊平” 元素,比如将多个小集合合并成一个大集合。
  • 这里 List::stream 表示将每个子集合转换为流。

6. reduce():累积操作

reduce() 用于将流中的元素进行聚合操作,例如求和、求最大值等。

示例

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 求和
int sum = numbers.stream()
                 .reduce(0, Integer::sum);
System.out.println(sum); // Output: 15

理解要点

  • reduce 需要一个初始值(这里是 0),一个二元操作(Integer::sum)。
  • 操作会从初始值开始累加流中的元素。

7. distinct():去重

distinct() 方法用于去掉流中的重复元素,最终返回一个去重后的流。

示例

List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 4);

// 去重
List<Integer> distinctNumbers = numbers.stream()
                                       .distinct()
                                       .collect(Collectors.toList());
System.out.println(distinctNumbers); // Output: [1, 2, 3, 4]

理解要点
distinct() 会基于元素的 equals()hashCode() 方法判断是否重复。


8. sorted():排序

sorted() 可以对流中的元素进行排序,支持自然排序和自定义排序。

示例

List<Integer> numbers = Arrays.asList(5, 3, 1, 4, 2);

// 升序排序
List<Integer> sortedNumbers = numbers.stream()
                                     .sorted()	//Comparator.comparing()
                                     .collect(Collectors.toList());
System.out.println(sortedNumbers); // Output: [1, 2, 3, 4, 5]

理解要点

  • 默认情况下,sorted() 使用自然排序(升序)。
  • 如果需要自定义排序,可以传入比较器。

9. limit():截断流

limit() 用于获取流中的前 n 个元素。

示例

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

// 只获取前 3 个元素
List<Integer> limitedNumbers = numbers.stream()
                                      .limit(3)
                                      .collect(Collectors.toList());
System.out.println(limitedNumbers); // Output: [1, 2, 3]

实际场景
例如分页功能中,可以结合 skip() 实现数据的分页查询。


10. skip():跳过前 n 个元素

skip() 用于跳过流中的前 n 个元素,返回剩余元素的流。

示例

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

// 跳过前 4 个元素
List<Integer> skippedNumbers = numbers.stream()
                                      .skip(4)
                                      .collect(Collectors.toList());
System.out.println(skippedNumbers); // Output: [5, 6, 7]

结合 limit()

// 获取第 4 到第 6 个元素
List<Integer> subList = numbers.stream()
                               .skip(3)
                               .limit(3)
                               .collect(Collectors.toList());
System.out.println(subList); // Output: [4, 5, 6]

实际场景
跳过已处理数据,优化批量处理的效率。


11. anyMatch() / allMatch() / noneMatch():条件匹配

这三个方法用于判断流中的元素是否符合条件:

  • anyMatch():是否有任意一个元素满足条件。
  • allMatch():所有元素都满足条件。
  • noneMatch():没有任何元素满足条件。

示例

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 是否存在大于 4 的元素
boolean anyMatch = numbers.stream().anyMatch(n -> n > 4);
System.out.println(anyMatch); // Output: true

// 是否所有元素都小于 6
boolean allMatch = numbers.stream().allMatch(n -> n < 6);
System.out.println(allMatch); // Output: true

// 是否没有大于 5 的元素
boolean noneMatch = numbers.stream().noneMatch(n -> n > 5);
System.out.println(noneMatch); // Output: true

实际场景

  • 数据校验,例如用户输入是否符合条件。
  • 筛选判断,例如是否存在符合条件的订单。

12. findFirst() 和 findAny()

  • findFirst():返回流中的第一个元素,通常在顺序流中使用。
  • findAny():返回流中的任意一个元素,适合并行流操作,效率更高。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 使用 findFirst 方法查找第一个元素
Optional<Integer> first = numbers.stream().findFirst();

// 使用 findAny 方法查找任意一个元素
Optional<Integer> any = numbers.stream().findAny();

// 输出结果
System.out.println("First: " + first.orElse(null)); // Output: 1
System.out.println("Any: " + any.orElse(null));     // Output: 1 或其他任意元素
    

特别说明:

  • 顺序流 中,findAny() 的行为与 findFirst() 相似,通常会返回第一个元素。
  • 并行流 中,findAny() 可能会返回流中任意位置的元素,以提高性能。

13. max() / min():获取最大值或最小值

max()min() 用于根据比较器找出流中的最大或最小元素。

示例

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 获取最大值
Optional<Integer> max = numbers.stream().max(Integer::compareTo);
System.out.println(max.get()); // Output: 5

// 获取最小值
Optional<Integer> min = numbers.stream().min(Integer::compareTo);
System.out.println(min.get()); // Output: 1

理解要点

  • max()min() 返回 Optional 对象,需使用 .get() 获取值。
  • 可以使用自定义比较器实现复杂比较逻辑。

14. peek():调试流中的元素

peek() 用于对流中的每个元素执行操作,但不会中断流的操作。适合用来调试。

示例

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 打印调试信息
List<String> filteredNames = names.stream()
                                  .peek(name -> System.out.println("Before filter: " + name))
                                  .filter(name -> name.length() > 3)
                                  .peek(name -> System.out.println("After filter: " + name))
                                  .collect(Collectors.toList());
System.out.println(filteredNames);

输出

Before filter: Alice  
After filter: Alice  
Before filter: Bob  
Before filter: Charlie  
After filter: Charlie  
[Alice, Charlie]

理解要点
peek() 通常用于调试,打印中间状态,帮助排查问题。


15. count():统计流中的元素个数

count() 是一个终止操作,用于统计流中元素的个数。

示例

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 统计名字长度大于 3 的元素个数
long count = names.stream()
                  .filter(name -> name.length() > 3)
                  .count();
System.out.println(count); // Output: 2

实际场景
数据统计,比如计算符合条件的记录数量。


16. groupBy():将流中的元素按照指定的条件分组

sorted(Comparator) 支持根据自定义逻辑对流中的元素进行排序。

示例

 List<String> words = Arrays.asList("dog", "cat", "elephant", "rat", "ant");

// 按字符串长度分组
Map<Integer, List<String>> groupedByLength = words.stream()
    .collect(Collectors.groupingBy(String::length));

System.out.println(groupedByLength); 
// 输出: {3=[dog, cat, rat, ant], 8=[elephant]}

理解要点

  • Collectors.groupingBy() 返回值作为键。

17. partitioningBy():将流中的元素按照指定的条件分成两个部分。

sorted(Comparator) 支持根据自定义逻辑对流中的元素进行排序。

示例

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

// 根据是否为偶数进行分区
Map<Boolean, List<Integer>> partitionedByEvenOdd = numbers.stream()
    .collect(Collectors.partitioningBy(num -> num % 2 == 0));

System.out.println(partitionedByEvenOdd); 
// 输出: {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8]}

分区结合统计信息:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

// 分区后统计每组的元素数量
Map<Boolean, Long> countByEvenOdd = numbers.stream()
.collect(Collectors.partitioningBy(num -> num % 2 == 0, Collectors.counting()));

System.out.println(countByEvenOdd); 
// 输出: {false=5, true=4}

总结补充

通过以上 17 个常用操作,我们可以看到 Stream API 在数据处理方面提供了强大的功能。总结一些常见的使用场景:

  1. 数据过滤与筛选filter()distinct()limit()
  2. 数据转换与聚合map()flatMap()reduce()
  3. 数据统计与校验count()allMatch()anyMatch()
  4. 调试与排序peek()sorted()max()min()

合理使用 Stream API,能够让代码更加简洁、可读性更强,同时提高开发效率。希望这篇文章对大家有帮助!!👍★(疯狂暗示)


博客主页: 总是学不会.