Java中的stream流的用法


title: Java中的stream流的用法

date: 2026-05-04

tags:

  • Java
    categories:
  • 技术
  • Java基础
    cover: /BlogANnian/images/cover/stream.png
    description: 在 java 中,涉及到对数组、集合等集合类元素的操作时,通常我们使用的是循环的方式进行逐个遍历处理,或者使用 stream 流的方式进行处理。

Java中的stream流的用法

在 java 中,涉及到对数组、集合等集合类元素的操作时,通常我们使用的是循环的方式进行逐个遍历处理,或者使用 stream 流的方式进行处理。

1、什么是 Stream?

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java集合运算和表达的高阶抽象。StreamAPl可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等。

2、创建 Stream 流

stream() 使用 stream() 创建串行流

java 复制代码
Stream<String> stream = stringList.stream();

**parallelStream() ** 使用 parallelStream() 创建并行流

java 复制代码
Stream<String> stringStream = stringList.parallelStream();

通过Stream创建流

可以使用Stream类提供的方法,直接返回一个由指定元素组成的流。

java 复制代码
Stream<String> stream = Stream.of("Hello", "HelloWorld", "World");

如以上代码,直接通过of方法,创建并返回一个Stream

3、Stream 流常用操作

负责对Stream进行处理操作,并返回一个新的Stream对象,中间管道操作可以进行叠加

用的都是Hutool工具类ObjectUtil、JSONUtil

forEach forEach来迭代流中的每个数据,输出所有元素

java 复制代码
stringList.forEach(System.out::println);

map 用于映射每个元素到对应的结果,将流中的每一个元素T映射为R,将已有元素转换为另一个对象类型,一对一逻辑,返回新的stream流,保持嵌套结构

java 复制代码
List<Long> list = stringList.stream()
                .map(size -> ((Number) size).longValue())
                .toList();

filter filter是过滤操作,返回结果为true的数据;

java 复制代码
List<String> Lists = oldList.stream()
                .filter(obj -> ObjectUtil.isNotNull(obj))
  							.toList();
// :: 是一种方法引用,它可以将一个方法引用转换为一个Lambda表达式,这里的ObjectUtil::isNotNull就是一个方法引用,等价于 obj -> ObjectUtil.isNotNull(obj)
List<String> Lists = oldList.stream()
                .filter(ObjectUtil::isNotNull)
                .map(Object::toString)
                .toList();

**flatMap **将已有元素转换为另一个对象类型,一对多逻辑,即原来一个元素对象可能会转换为1个或者多个新类型的元素,返回新的stream流

复制代码
一个元素变成多个元素(集合的扁平化)
java 复制代码
Map<String, Long> tagCountMap = tagsLists.stream()
                // 将每个JSON字符串 "[标签1,标签2]" 展开成多个标签
 								// "[标签1,标签2,标签3]", "[标签1,标签2,标签3]" ==> "标签1","标签2","标签3","标签1","标签2","标签3"
                .flatMap(tags -> (JSONUtil.toList(tags, String.class)).stream())
复制代码
嵌套集合扁平化
java 复制代码
List<List<String>> nestedList = Arrays.asList(
    Arrays.asList("a", "b"),
    Arrays.asList("c", "d")
);

// flatMap 将 List<List<String>> 转为 List<String>
List<String> flatList = nestedList.stream()
    .flatMap(List::stream)
    .toList();
// 结果: ["a", "b", "c", "d"]
复制代码
Optional 去空值
java 复制代码
List<Optional<String>> optionals = Arrays.asList(
    Optional.of("hello"),
    Optional.empty(),
    Optional.of("world")
);

List<String> result = optionals.stream()
    .flatMap(opt -> opt.stream())  // 自动过滤掉 empty
    .toList();
// 结果: ["hello", "world"]
复制代码
Map 的键值对展开
java 复制代码
Map<String, List<String>> map = Map.of(
    "fruits", Arrays.asList("apple", "banana"),
    "vegetables", Arrays.asList("carrot", "tomato")
);

// 将所有列表的值展开
List<String> allItems = map.values().stream()
    .flatMap(List::stream)
    .toList();
// 结果: ["apple", "banana", "carrot", "tomato"]
复制代码
条件过滤 + 展开组合
java 复制代码
List<String> sentences = Arrays.asList(
                "hello world",
                "java stream",
                "flat map"
        );

// 只展开长度大于5的句子的单词
        List<String> words = sentences.stream()
                .flatMap(s -> Arrays.stream(s.split(" ")))
                .filter(s -> s.length() > 5)
                .toList();
// 结果: ["stream"]

mapToLong(也适应其他格式:mapToDouble等)

java 复制代码
long count = list.stream()
                    .mapToLong(obj -> (Long) obj) // 将Object转换为Long
                    .sum();// 计算总使用大小

**limit ** 获取指定数量的流

java 复制代码
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
numbers.stream().limit(3).forEach(System.out::println); //1, 2, 3

imit 返回 Stream 的前面n个元素;skip 则是扔掉前 n个元素

**skip ** 扔掉前 n个元素

java 复制代码
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
numbers.stream().skip(3).forEach(System.out::println); // 4, 5, 6, 7

distinct 去重

java 复制代码
List<Integer> numbers = Arrays.asList(4, 2, 3, 4, 5, 6, 4);
numbers.stream().distinct.forEach(System.out::println); // 4, 2, 3, 5, 6

sortd sorted 方法用于对流进行排序

java 复制代码
List<Integer> numbers = Arrays.asList(7, 2, 6, 4, 5, 3, 1);
numbers.stream().sorted().forEach(System.out::println); //1, 2, 3, 4, 5, 6, 7 默认升序

sorted(Comparator com) 添加判断方法

java 复制代码
.stream().sorted(Comparator.comparing(Integer::intValue));
// 降序排序
.stream().sorted(Comparator.comparing(Integer::intValue).reversed());

//==================================================================
Map<String, Long> countMap = new HashMap<>();
countMap.put("a", 5L);
countMap.put("b", 6L);
countMap.put("c", 1L);
countMap.put("d", 9L);
countMap.
  .entrySet()// 键值对视图,Map 没有实现 Streamable 接口,需要先转换为 Collection【keySet(只需要键)values(只需要值)entrySet(最常用,同时访问键和值)】
  .stream()
  .sorted((entry1, entry2) 
          -> Long.compare(
                  entry2.getValue(),
                  entry1.getValue()// 先2后1,降序排列
                )
           )
  .forEach(System.out::println);
// d,b,a,c

**peek ** 主要用于调试

peek = 偷看:查看流中数据但不修改

必须配合终端操作,否则不会执行,也就是peek后面得有结束操作.toList()等

主要用于调试,不要用它替代 map:peek 不应该改变数据

java 复制代码
.stream()
    .peek(map -> log.debug("原始数据: {}", map))  // 调试日志
    .map(res -> res + 1)
    .peek(response -> log.debug("转换结果: {}", response))  // 调试日志
    .toList();

4、Collectors 收集器

Stream结果收集操作的本质,其实就是将Stream中的元素通过收集器定义的函数处理逻辑进行加工,然后输出加工后的结果

Collectors工具类通过的Collector实现类

方法 说明
toList 将流中的元素收集到一个 List 中
toSet 将流中的元素收集到一个 Set 中
toCollection 将流中的元素收集到一个 Collection 中
toMap 将流中的元素映射收集到一个 Map 中
counting 统计流中的元素个数
summingInt 计算流中指定 int 字段的累加和。针对不同类型的数字类型,有不同的方法,比如 summingDouble 等
averagingInt 计算流中指定 int 字段的平均值。针对不同类型的数字类型,有不同的方法,比如 averagingLong 等
joining 将流中所有元素(或者元素的指定字段)字符串值进行拼接,可以指定拼接连接符,或者首尾拼接字符
maxBy 根据给定的比较器,选择出值最大的元素
minBy 根据给定的比较器,选择出值最小的元素
groupingBy 根据给定的分组函数的值进行分组,输出一个 Map 对象
partitioningBy 根据给定的分区函数的值进行分区,输出一个 Map 对象,且 key 始终为布尔值类型
collectingAndThen 包裹另一个收集器,对其结果进行二次加工转换
reducing 从给定的初始值开始,将元素进行逐个处理,最终将所有元素计算为最终的 1 个值输出

恒等处理 Collectors

所谓恒等处理,指的就是Stream的元素在经过Collector函数处理前后完全不变,例如toList()操作,只是最终将结果从Stream中取出放入到List对象中,并没有对元素本身做任何的更改处理

toList() 是新规范 Java 16+

分组 Collectors 分组

groupingBy()操作需要指定两个关键输入,即分组函数值收集器

  • 分组函数:一个处理函数,用于基于指定的元素进行处理,返回一个用于分组的值(即分组结果HashMap的Key值),对于经过此函数处理后返回值相同的元素,将被分配到同一个组里。
  • 值收集器:对于分组后的数据元素的进一步处理转换逻辑,此处还是一个常规的Collector收集器,和collect()方法中传入的收集器完全等同(可以想想俄罗斯套娃,一个概念)。

对于groupingBy分组操作而言,分组函数与值收集器二者必不可少。为了方便使用,在Collectors工具类中,提供了两个groupingBy重载实现,其中有一个方法只需要传入一个分组函数即可,这是因为其默认使用了toList()作为值收集器

java 复制代码
// 常规分组可以仅传入一个分组函数
Map<String, List<String>> tagCountMap = tagsLists.stream()
                // "标签1","标签2","标签3","标签1","标签2","标签3"
                // 使用Collectors.groupingBy()方法进行分组,并使用Collectors.counting()方法进行计数
                .collect(Collectors.groupingBy(tag -> tag));
/* 结果:
{
  标签1=[标签1, 标签1],
  标签2=[标签2, 标签2],
  标签3=[标签3, 标签3]
}
**/
// =================================================
// 如果需要对数据额外处理,就可以传入分组函数,比如下面的计数
Map<String, Long> tagCountMap = tagsLists.stream()
                // "标签1","标签2","标签3","标签1","标签2","标签3"
                // 使用Collectors.groupingBy()方法进行分组,并使用Collectors.counting()方法进行计数
                .collect(Collectors.groupingBy(tag -> tag, Collectors.counting()));
// 结果:{标签1=2, 标签2=2, 标签3=2}
// =================================================
/**
* key类型仅为布尔值
* 通过Collectors.partitioningBy()提供的分区收集器来实现
**/
Map<Boolean, Long> tagCountMap = tagsLists.stream()
                // "标签1","标签2","标签3","标签1","标签2","标签3",""
                // 使用Collectors.groupingBy()方法进行分组,并使用Collectors.counting()方法进行计数
                .collect(Collectors.partitioningBy(
                		tag -> StrUtil.isNotBlank(tag),
                  	Collectors.counting()
                ));
// 结果:{false=1, true=6}

归约汇总 Collectors 终止管道

通过终止管道操作之后,Stream流将会结束,最后可能会执行某些逻辑处理,或者是按照要求返回某些执行后的结果数据。对于归约汇总类的操作,Stream流中的元素逐个遍历,进入到Collector处理函数中,然后会与上一个元素的处理结果进行合并处理,并得到一个新的结果,以此类推,直到遍历完成后,输出最终的结果

API 功能说明
count() 返回 stream 处理后最终的元素个数
max() 返回 stream 处理后的元素最大值
min() 返回 stream 处理后的元素最小值
findFirst() 找到第一个符合条件的元素时则终止流处理
findAny() 找到任何一个符合条件的元素时则退出流处理,这个对于串行流时与 findFirst 相同,对于并行流时比较高效,任何分片中找到都会终止后续计算逻辑
anyMatch() 返回一个 boolean 值,类似于 isContains (), 用于判断是否有符合条件的元素
allMatch() 返回一个 boolean 值,用于判断是否所有元素都符合条件
noneMatch() 返回一个 boolean 值,用于判断是否所有元素都不符合条件
collect() 将流转换为指定的类型,通过 Collectors 进行指定
toArray() 将流转换为数组
iterator() 将流转换为 Iterator 对象
foreach() 无返回值,对元素进行逐个遍历,然后执行给定的处理逻辑

这里需要补充提醒下,一旦一个Stream被执行了终止操作之后,后续便不可以再读这个流执行其他的操作了,否则会报错

5、总结

Stream API 的优势,相比于传统的开发方式

  • 简洁性和可读性:代码更简洁,避免了显式的循环。
  • 函数式编程支持:通过高阶函数提供更高的灵活性和可重用性。
  • 并行处理:轻松使用 parallelStream() 实现并行处理,提高性能。
  • 惰性计算和延迟求值:避免不必要的计算,提高性能。
  • 丰富的聚合和转换功能:提供强大的数据聚合、转换、过滤等功能。
  • 函数式思维:鼓励不修改数据的不可变式操作。

参考

https://juejin.cn/post/7118991438448164878

https://www.cnblogs.com/softwarearch/p/16490440.html

https://www.yuque.com/xiaoheizuan/txpysq/lsopxqxn191f6k0z

https://blog.csdn.net/2303_80195175/article/details/154076110

相关推荐
1104.北光c°1 小时前
【AI核心概念讲解】一口气搞懂 Agent:干翻传统后端!自主循环决策的秘密,ReAct 与 Plan-and-Execute 范式
java·人工智能·程序人生·ai·agent·react·智能体
Jul1en_1 小时前
Claude 迁移 Codex 工作流迁移与更新
java·服务器·前端·后端·ai编程
未若君雅裁2 小时前
Spring Statemachine 实战入门:从零实现一个订单状态流转 Demo
java·spring·状态模式
早日退休!!!2 小时前
操作系统锁
java·开发语言
研究点啥好呢2 小时前
快手多模态算法工程师面试题精选:10道高频考题+答案解析
java·开发语言·人工智能·ai·面试·笔试
遗憾随她而去.2 小时前
Java学习(一)
java·开发语言·学习
陌路物是人非2 小时前
记一个controller入参为null的奇怪问题
java·开发语言
小瓦码J码2 小时前
Spring boot 如何自定义加密解密数据库连接配置
java
XiYang-DING2 小时前
【Java EE】JUC的常见类(Callable、ReentrantLock、Semaphore和CountDownLatch )
java·java-ee