Java中对stream的操作

131 阅读6分钟

Java中对stream的操作

在Java中,Stream API 提供了一种高效处理数据的方式。Stream操作可以分为中间操作(Intermediate Operations)和终端操作(Terminal Operations)。中间操作可以进一步分为无状态(Stateless)和有状态(Stateful)操作,而终端操作可以分为短路(Short-circuiting)和非短路(Non-short-circuiting)操作。

1. 中间操作(Intermediate Operations)

  • 无状态(Stateless)中间操作

    • 这些操作是不保留状态的。它们不需要关于其他元素的信息就可以处理每个传入的元素。例如,mapfilter就是无状态操作。每个元素的处理只依赖于该元素本身,而与流中其他元素无关。

    • 示例:

      • map:对流中的每个元素应用一个函数。
      • filter:根据给定的谓词过滤流中的元素。
  • 有状态(Stateful)中间操作

    • 这些操作需要关于之前处理过的元素的信息。例如,排序操作需要知道之前的元素来确定当前元素的正确位置。

    • 示例:

      • sorted:对流中的元素进行排序。
      • distinct:返回一个只包含不同元素的流。

2. 终端操作(Terminal Operations)

  • 短路(Short-circuiting)终端操作

    • 这些操作不一定需要处理整个流就可以得到结果。它们可以在达到某种条件时“短路”并提前结束处理流程。

    • 示例:

      • findFirst:返回流中的第一个元素。
      • anyMatch:检查流中的元素是否有任何一个符合给定的谓词。
  • 非短路(Non-short-circuiting)终端操作

    • 这些操作需要处理流中的所有元素才能得到结果。

    • 示例:

      • forEach:对流中的每个元素执行一个操作。
      • collect:将流转换为其他形式,例如集合。

中间操作是惰性的,只有在执行终端操作时,所有中间操作才会被执行。这种设计使得操作可以优化执行,例如通过合并或消除不必要的处理步骤。

无状态(Stateless)中间操作举例

1. filter

filter操作使用一个谓词来测试每个元素,仅保留满足条件的元素。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
List<String> filteredNames = names.stream()
                                  .filter(name -> name.startsWith("A"))
                                  .collect(Collectors.toList());
// 结果: ["Alice"]

2. map

map操作对流中的每个元素应用一个函数,并将其转换成另一种形式。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
List<Integer> nameLengths = names.stream()
                                 .map(String::length)
                                 .collect(Collectors.toList());
// 结果: [5, 3, 7, 5]

3. flatMap

flatMap操作将流中的每个元素转换成另一个流,然后将这些流连接起来成为一个流。

List<List<String>> listOfLists = Arrays.asList(
    Arrays.asList("Alice", "Bob"),
    Arrays.asList("Charlie", "Diana")
);
List<String> flatList = listOfLists.stream()
                                   .flatMap(List::stream)
                                   .collect(Collectors.toList());
// 结果: ["Alice", "Bob", "Charlie", "Diana"]

4. peek

peek操作会对每个元素执行操作,主要用于调试。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
List<Integer> processedNumbers = numbers.stream()
                                        .peek(number -> System.out.println("Before map: " + number))
                                        .map(number -> number * 2)
                                        .peek(number -> System.out.println("After map: " + number))
                                        .collect(Collectors.toList());
// 控制台输出元素处理前后的状态

5. distinct

distinct操作返回一个流,其中包含了原始流中不同的元素。

List<Integer> numbersWithDuplicates = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
List<Integer> uniqueNumbers = numbersWithDuplicates.stream()
                                                   .distinct()
                                                   .collect(Collectors.toList());
// 结果: [1, 2, 3, 4, 5]

6. sorted

sorted操作返回一个流,其中的元素按自然顺序排序,也可以提供一个自定义的比较器。

List<String> names = Arrays.asList("Charlie", "Diana", "Alice", "Bob");
List<String> sortedNames = names.stream()
                                .sorted()
                                .collect(Collectors.toList());
// 结果: ["Alice", "Bob", "Charlie", "Diana"]

7. limit

limit操作截取流,使其最大长度不超过给定数量。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> limitedNumbers = numbers.stream()
                                       .limit(3)
                                       .collect(Collectors.toList());
// 结果: [1, 2, 3]

8. skip

skip操作返回一个丢弃了原始流的前N个元素的流。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> skippedNumbers = numbers.stream()
                                       .skip(2)
                                       .collect(Collectors.toList());
// 结果: [3, 4, 5, 6]

有状态(Stateful)中间操作举例

1. distinct

distinct操作返回一个流,其中包含了原始流中不同的元素。这需要维护一个状态来记录已经出现过的元素。

List<Integer> numbersWithDuplicates = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
List<Integer> uniqueNumbers = numbersWithDuplicates.stream()
                                                   .distinct()
                                                   .collect(Collectors.toList());
// 结果: [1, 2, 3, 4, 5]

2. sorted

sorted操作返回一个流,其中的元素按自然顺序排序,也可以提供一个自定义的比较器。排序操作需要知道之前的元素来确定当前元素的正确位置。

List<String> names = Arrays.asList("Charlie", "Diana", "Alice", "Bob");
List<String> sortedNames = names.stream()
                                .sorted()
                                .collect(Collectors.toList());
// 结果: ["Alice", "Bob", "Charlie", "Diana"]

3. limit

虽然limit在某些情况下可以被认为是无状态的,但在涉及无序流时(如并行流),它可能需要维护状态以确保限制操作的正确性。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> limitedNumbers = numbers.stream()
                                       .limit(5)
                                       .collect(Collectors.toList());
// 结果: [1, 2, 3, 4, 5]

4. skip

limit类似,skip在处理无序流时也可能需要维护状态。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> skippedNumbers = numbers.stream()
                                       .skip(5)
                                       .collect(Collectors.toList());
// 结果: [6, 7, 8, 9, 10]

短路(Short-circuiting)终端操作举例

1. anyMatch

anyMatch操作检查流中的元素是否有任何一个符合给定的谓词。如果找到符合条件的元素,它会立即返回true并结束处理。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
boolean hasNameStartingWithA = names.stream()
                                    .anyMatch(name -> name.startsWith("A"));
// 结果: true

2. allMatch

allMatch操作检查流中的所有元素是否都符合给定的谓词。如果所有元素都符合条件,或者流是空的,它返回true

List<Integer> numbers = Arrays.asList(2, 4, 6, 8, 10);
boolean allEven = numbers.stream()
                         .allMatch(n -> n % 2 == 0);
// 结果: true

3. noneMatch

noneMatch操作检查流中的元素是否都不符合给定的谓词。如果没有任何元素符合条件,它返回true

List<Integer> numbers = Arrays.asList(2, 4, 6, 8, 10);
boolean noneOdd = numbers.stream()
                         .noneMatch(n -> n % 2 != 0);
// 结果: true

4. findFirst

findFirst操作返回流中的第一个元素,如果流为空,则返回一个空的Optional

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
Optional<String> first = names.stream()
                              .findFirst();
// 结果: Optional[Alice]

5. findAny

findAny操作返回流中的任意一个元素。在并行流中使用时,这通常会比findFirst更高效。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
Optional<String> any = names.stream()
                            .findAny();
// 结果: Optional[某个元素,比如Alice]

非短路(Non-short-circuiting)终端操作举例

1. forEach

forEach操作对流中的每个元素执行给定的操作。它通常用于产生副作用(如打印)。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
names.stream()
     .forEach(System.out::println);
// 输出每个名字

2. collect

collect操作是一个非常强大的终端操作,它可以将流转换成其他形式,如集合。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
List<String> list = names.stream()
                         .collect(Collectors.toList());
// 将流转换为列表

3. count

count操作返回流中的元素个数。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
long count = names.stream()
                  .count();
// 结果: 4

4. reduce

reduce操作将流中的元素组合起来,使用一个累积函数,并返回一个可选的累积结果。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
                 .reduce(0, (a, b) -> a + b);
// 结果: 15

5. maxmin

这些操作分别返回流中的最大值和最小值,根据提供的比较器。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
OptionalInt max = numbers.stream()
                         .mapToInt(Integer::intValue)
                         .max();
// 结果: OptionalInt[5]

OptionalInt min = numbers.stream()
                         .mapToInt(Integer::intValue)
                         .min();
// 结果: OptionalInt[1]

6. summaryStatistics

对于原始类型流(如IntStreamLongStreamDoubleStream),summaryStatistics会收集统计信息,如总数、总和、最小值、最大值和平均值。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
IntSummaryStatistics stats = numbers.stream()
                                    .mapToInt(Integer::intValue)
                                    .summaryStatistics();
// 获取统计信息

7. forEachOrdered

forEachOrdered保留了流的遇到顺序,即使在并行流中也是如此。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
names.parallelStream()
     .forEachOrdered(System.out::println);
// 按照原始列表的顺序输出每个名字