什么是Stream流

文章目录

    • 一、创建流
      • [1.1 流的类型](#1.1 流的类型)
      • [1.2 创建 Stream 的常见操作](#1.2 创建 Stream 的常见操作)
    • [二、 中间操作](#二、 中间操作)
      • [2.1 流的映射](#2.1 流的映射)
      • [2.2 流的过滤](#2.2 流的过滤)
      • [2.3 其他中间操作](#2.3 其他中间操作)
    • [三、 终端操作](#三、 终端操作)
      • [3.1 reduce() 的使用方法](#3.1 reduce() 的使用方法)
      • [3.2 *short-circuiting* 方法](#3.2 short-circuiting 方法)
      • [3.3 其他的终端操作](#3.3 其他的终端操作)
    • 参考资料

一、创建流

1.1 流的类型

在 Java 8 中, 集合接口有两个方法来生成流:

  • stream() − 为集合创建串行流。
  • parallelStream() − 为集合创建并行流。

stream() 方法在 Collection接口

1.2 创建 Stream 的常见操作

举个例子,以 stream() 函数为例,可以通过集合、数组、I/O 通道等方式创建流。

java 复制代码
//  从 List 创建流
List<String> list = Arrays.asList("apple", "banana", "orange");
Stream<String> streamFromList = list.stream();

// 从数组创建 IntStream:
int[] array = {1, 2, 3, 4, 5};
IntStream streamFromArray = Arrays.stream(array);

// 使用 Stream.of 创建流:
Stream<String> streamFromValues = Stream.of("apple", "banana", "orange");

事实上,创建流还有很多方式,更多细节查看 Creating Streams - Dev.java

二、 中间操作

中间操作是 map-filter-reduce 算法中 map 和 filter 实现。

中间操作会返回一个新的 Stream,因此可以链式调用 。这些操作是惰性的,即它们不会立即执行,而是在有终端操作时才会执行。这意味着中间操作只是定义了流的转换规则,实际处理会推迟到终端操作执行时。

具体细节查看 Reducing a Stream - Dev.java

2.1 流的映射

(1) map()

映射流包括使用Function转换其元素。

您可以使用 map() 方法将一个流映射到另一个流,该方法将Function作为参数。映射流意味着该流处理的所有元素都将使用Function进行转换。举个例子,示例代码如下:

java 复制代码
List<String> strings = List.of("one", "two", "three", "four");
Function<String, Integer> toLength = String::length;
Stream<Integer> ints = strings.stream()
                              .map(toLength);

需要注意的是,这段代码里面没有终端操作,因此这段代码没有做任何事

另外,有三种方法可以从 Stream 转到原始类型的流:mapToInt(), mapToLong() and mapToDouble()。举个例子,示例代码如下:

java 复制代码
List<String> strings = List.of("one", "two", "three", "four");
IntSummaryStatistics stats = strings.stream()
                                    .mapToInt(String::length)
                                    .summaryStatistics();
System.out.println("stats = " + stats);

输出结果为

 stats = IntSummaryStatistics{count=4, sum=15, min=3, average=3,750000, max=5}

(2)flatmap()

在 Java 中,flatMap() 是流(Stream)类的一个中间操作,用于处理包含多个元素的流。它的作用是将每个流元素映射为一个流,然后将这些流合并成一个新的流

举个例子,假设有一个列表,其中包含多个单词,我们希望将每个单词拆分为字符,并将所有字符合并到一个新的流中:

List<String> words = Arrays.asList("Hello", "World");

// 使用 flatMap 将每个单词拆分为字符,并合并为一个新流
List<String> characters = words.stream()
                              .flatMap(word -> Arrays.stream(word.split("")))
                              .collect(Collectors.toList());

System.out.println(characters);

在这个例子中,flatMap 将每个单词映射为一个字符流,然后将这些字符流合并成一个新的流,最终得到一个包含所有字符的列表。输出结果为:

[H, e, l, l, o, W, o, r, l, d]

2.2 流的过滤

过滤是在流(Stream)上进行元素过滤。此方法适用于对象流和原始类型流。

举个例子,

java 复制代码
List<String> strings = List.of("one", "two", "three", "four");
long count = strings.stream()
                    .map(String::length)
                    .filter(length -> length == 3)
                    .count();
System.out.println("count = " + count);   // 结果为:count = 2

其中,count()是 Stream API的另一个终端操作,它只计算已处理元素的数量

2.3 其他中间操作

  • distinct()方法 : 使用hashCode()和equals()方法来发现重复项, 然后去除重复项

  • sorted(): 有一个comparator的重载,该比较器将用于比较和排序流的元素。如果您不提供比较器,则Stream API假定您的流的元素具有可比性。如果不是,则会引发ClassCastException。

  • skip(long n) 方法: 用于跳过流中的前 n 个元素,返回一个新的流。如果流的长度小于 n,则返回一个空流。

  • limit(long maxSize) 方法: 用于限制流的大小,返回一个包含不超过 maxSize 个元素的新流。

  • concat() : 用于连接两个流,将它们合并成一个新的流。 建议用 flatMap() 来替代这个函数,举个例子:

    java 复制代码
    List<Integer> list0 = List.of(1, 2, 3);
    List<Integer> list1 = List.of(4, 5, 6);
    List<Integer> list2 = List.of(7, 8, 9);
    
    // 1st pattern: concat
    List<Integer> concat = 
        Stream.concat(list0.stream(), list1.stream())
              .collect(Collectors.toList());
    
    // 2nd pattern: flatMap
    List<Integer> flatMap =
        Stream.of(list0.stream(), list1.stream(), list2.stream())
              .flatMap(Function.identity())
              .collect(Collectors.toList());
    
    System.out.println("concat  = " + concat);
    System.out.println("flatMap = " + flatMap);
  • peek(): ,用于对流中的每个元素执行操作,同时保持流的原始结构。它返回的流与原始流相同,但可以在执行 peek() 时观察到中间的操作。这个可以用来调试流

    java 复制代码
    List<String> strings = List.of("one", "two", "three", "four");
    List<String> result =
            strings.stream()
                    .peek(s -> System.out.println("Starting with = " + s))
                    .filter(s -> s.startsWith("t"))
                    .peek(s -> System.out.println("Filtered = " + s))
                    .map(String::toUpperCase)
                    .peek(s -> System.out.println("Mapped = " + s))
                    .collect(Collectors.toList());
    System.out.println("result = " + result);

三、 终端操作

终端操作(Terminal Operations)是 map-filter-reduce 算法中 reduce 实现。

终端操作(Terminal Operations)是流操作中的最后一步,它们产生最终的结果或副作用。终端操作会触发流的遍历,并生成最终的结果或执行一些操作。在执行后,流就会被消耗,不能再使用。

如果流不以终端操作结束,则流不会处理任何数据。

3.1 reduce() 的使用方法

reduce() 方法: 对流中的元素进行归约操作,返回一个 Optional。(官方不建议使用这个方法)例如:

java 复制代码
Optional<String> concatenated = words.stream().reduce((s1, s2) -> s1 + ", " + s2);

具体细节查看:Reducing a Stream - Dev.java

3.2 short-circuiting 方法

"short-circuiting" 方法是指在处理流时,当满足某个条件后,不再继续处理剩余的元素,提前结束处理流的过程。这可以帮助提高性能,因为它避免了对整个流的遍历。

在 Java 的 Stream API 中,有一些操作是 short-circuiting 的,例如:

  1. findFirst(): 返回第一个符合条件的元素后,立即结束流的处理。
  2. findAny(): 返回任意符合条件的元素后,立即结束流的处理。
  3. limit(n): 限制流的大小为 n,一旦达到这个大小,就不再继续处理。
  4. anyMatch(predicate): 一旦有任意一个元素满足条件,就结束流的处理。
  5. allMatch(predicate): 一旦有任意一个元素不满足条件,就结束流的处理。
  6. noneMatch(predicate): 一旦有任意一个元素满足条件,就结束流的处理。

3.3 其他的终端操作

需要注意的是,尽量避免使用 reduce() 方法

以下是一些常见的终端操作:

  1. forEach(): 对流中的每个元素执行指定的操作。例如:

    java 复制代码
    codeList<String> words = Arrays.asList("apple", "banana", "orange");
    words.stream().forEach(System.out::println);
  2. count(): 返回流中元素的数量。例如:

    java 复制代码
    long count = words.stream().count();
  3. collect(): 将流中的元素收集到一个集合或其他数据结构中。例如:

    java 复制代码
    List<String> collectedList = words.stream().collect(Collectors.toList());
  4. min() 和 max(): 返回流中的最小或最大元素。例如:

    java 复制代码
    Optional<String> minElement = words.stream().min(String::compareTo);
    Optional<String> maxElement = words.stream().max(String::compareTo);
  5. anyMatch()、allMatch() 和 noneMatch(): 用于检查流中的元素是否匹配给定的条件。例如:

    java 复制代码
    codeboolean anyMatch = words.stream().anyMatch(s -> s.startsWith("a"));
    boolean allMatch = words.stream().allMatch(s -> s.length() > 3);
    boolean noneMatch = words.stream().noneMatch(s -> s.contains("z"));

具体细节查看 Adding a Terminal Operation on a Stream - Dev.java

参考资料

Stream - Dev.java

相关推荐
公贵买其鹿2 分钟前
List深拷贝后,数据还是被串改
java
PieroPc5 分钟前
Python 写的 智慧记 进销存 辅助 程序 导入导出 excel 可打印
开发语言·python·excel
2401_857439693 小时前
SSM 架构下 Vue 电脑测评系统:为电脑性能评估赋能
开发语言·php
SoraLuna3 小时前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos
xlsw_3 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹4 小时前
基于java的改良版超级玛丽小游戏
java
Dream_Snowar4 小时前
速通Python 第三节
开发语言·python
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭4 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫5 小时前
泛型(2)
java
超爱吃士力架5 小时前
邀请逻辑
java·linux·后端