java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java  stream流

Java中的Stream流式操作大全

作者:heartbeat..

Java 8引入的Stream API提供了一种高效、简洁的集合数据处理方式,基于函数式编程思想,支持惰性求值、一次性消费、无状态/有状态操作和并行处理,本文通过实例代码介绍java中的Stream流式操作,感兴趣的朋友跟随小编一起看看吧

一、介绍

  在 Java 8 及以上版本中Stream(流式操作) 是处理集合(Collection)、数组等数据的高效工具,它基于函数式编程思想,提供了声明式(而非命令式)的数据处理方式,核心优势是 简洁、高效、可并行
   Stream 本质是数据的 “流水线”数据从数据源(集合、数组等)流入,经过一系列中间操作(过滤、映射、排序等)的 “加工”,最终通过终端操作(统计、收集、遍历等)输出结果(或无输出)。
   这个 “流水线” 可以用 “工厂食品加工生产线” 来形象比喻,每个环节和 Stream 操作一一对应,特别好理解:

你可以把 Stream 想象成一条 自动化食品加工生产线

补充两个贴合 Stream 特性的细节:

  1. 原料只能用一次:同一批蔬菜(同一个 Stream)经过生产线(终端操作)后,就变成了成品,不能再放回仓库重新加工(Stream 一次性消费);
  2. 可选 “并行加工”:普通生产线是单条流水线(串行流),如果原料太多,可启动多条并行流水线(并行流),同时处理不同批次的原料,最后合并成品,效率更高。

二、特性

  1. 惰性求值:中间操作不会立即执行,只有调用终端操作时,才会触发整个流水线的计算(节省资源)。
  2. 一次性消费:一个 Stream 只能被使用一次,执行终端操作后会自动关闭,再次使用会抛出异常。
  3. 无状态 / 有状态操作
    • 无状态:每个元素的处理独立(如 filter(筛选)map(映射)),不依赖其他元素;
    • 有状态:处理元素需要依赖其他元素(如 sorted(自然排序)distinct(去重)limit(限制)),可能需要缓存数据。
  4. 并行支持:通过 parallelStream() 或 stream.parallel() 轻松实现并行处理(底层依赖 Fork/Join 框架)。

三、操作

完整的 Stream 操作分为 3 步:

  1. 创建 Stream:从数据源(集合、数组、生成器等)获取 Stream;
  2. 中间操作:对数据进行过滤、映射、排序等加工(可多个中间操作链式调用);
  3. 终端操作:触发计算,输出结果(或执行副作用,如遍历),Stream 生命周期结束。

流程图

集合/数组 → 创建 Stream → 中间操作1 → 中间操作2 → ... → 终端操作 → 结果

四、详解

1. 创建 Stream(数据源)

(1)从集合创建(最常用)

List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream(); // 串行流
Stream<String> parallelStream = list.parallelStream(); // 并行流

(2)从数组创建

String[] arr = {"x", "y", "z"};
Stream<String> stream = Arrays.stream(arr);
// 部分数组(从索引1到2,左闭右开)
Stream<String> partStream = Arrays.stream(arr, 1, 3);

(3)通过 Stream.of () 创建

Stream<Integer> stream = Stream.of(1, 2, 3, 4);
Stream<Object> emptyStream = Stream.empty(); // 空流

(4)无限流(生成器 / 迭代器)

  适用于需要动态生成数据的场景(需配合 limit 限制长度,否则无限循环):

// 1. generate:生成固定值或随机值(Supplier接口)
Stream<Double> randomStream = Stream.generate(Math::random).limit(5); // 5个随机数
// 2. iterate:迭代生成(种子 -> 下一个元素,UnaryOperator接口)
Stream<Integer> numStream = Stream.iterate(0, n -> n + 2).limit(5); // 0,2,4,6,8

2. 中间操作(加工数据)

中间操作返回新的 Stream,支持链式调用,常用操作如下:

操作功能描述示例(以 List<Integer> list = [1,2,3,4,5,6] 为例)
filter(Predicate)过滤元素(满足条件保留)list.stream().filter(n -> n % 2 == 0) → [2,4,6]
map(Function)元素映射(转换为另一种类型)list.stream().map(n -> n * 2) → [2,4,6,8,10,12]
flatMap(Function)扁平化映射(将流中的流展开为单个流)List<List<Integer>> list2 = [[1,2],[3,4]]; list2.stream().flatMap(Collection::stream) → [1,2,3,4]
distinct()去重(基于 equals() 方法)Stream.of(1,2,2,3).distinct() → [1,2,3]
sorted()自然排序(元素需实现 ComparableStream.of(3,1,2).sorted() → [1,2,3]
sorted(Comparator)自定义排序list.stream().sorted((a,b) -> b - a) → [6,5,4,3,2,1]
limit(long)限制返回前 N 个元素list.stream().limit(3) → [1,2,3]
skip(long)跳过前 N 个元素list.stream().skip(3) → [4,5,6]
peek(Consumer)遍历元素(无修改,常用于调试)list.stream().peek(System.out::println).count() → 打印所有元素后返回计数

3. 终端操作(触发计算)

终端操作会消耗 Stream,返回非 Stream 结果(或无返回),常用操作如下:

(1)遍历与消费

(2)聚合统计(返回单个值)

操作功能描述示例
count()返回元素个数(long 类型)Stream.of(1,2,3).count() → 3
max(Comparator)返回最大值(Optional 类型,避免空指针)Stream.of(1,2,3).max(Integer::compare) → Optional[3]
min(Comparator)返回最小值Stream.of(1,2,3).min(Integer::compare) → Optional[1]
findFirst()返回第一个元素(Optional,串行流高效)Stream.of(1,2,3).findFirst() → Optional[1]
findAny()返回任意一个元素(并行流高效)Stream.of(1,2,3).parallel().findAny() → 可能是 1/2/3
anyMatch(Predicate)是否存在满足条件的元素(短路求值)Stream.of(1,2,3).anyMatch(n -> n > 2) → true
allMatch(Predicate)所有元素是否满足条件(短路求值)Stream.of(1,2,3).allMatch(n -> n > 0) → true
noneMatch(Predicate)所有元素是否都不满足条件(短路求值)Stream.of(1,2,3).noneMatch(n -> n < 0) → true

   短路求值:一旦满足条件就停止计算(如 anyMatch 找到一个满足条件的元素就返回,无需遍历所有)。

(3)收集结果(collect(Collector))

  将 Stream 结果收集为集合、数组、Map 等,由Collectors 工具类提供了大量预定义收集器。

  ① 收集为集合

List<Integer> list = Stream.of(1,2,3).collect(Collectors.toList()); // 默认 ArrayList
List<Integer> linkedList = Stream.of(1,2,3).collect(Collectors.toCollection(LinkedList::new)); // 指定集合
Set<Integer> set = Stream.of(1,2,2).collect(Collectors.toSet()); // 去重集合

  ② 收集为数组

Integer[] arr = Stream.of(1,2,3).toArray(Integer[]::new); // 推荐(类型安全)
Object[] objArr = Stream.of(1,2,3).toArray(); // 不推荐(需要强转)

  ③ 收集为 Map(注意 key 唯一)

List<String> userList = Arrays.asList("id1:张三", "id2:李四", "id3:王五");
// key: 拆分后的id,value: 拆分后的姓名
Map<String, String> userMap = userList.stream()
    .map(user -> user.split(":")) // 拆分后为 String[]
    .collect(Collectors.toMap(
        arr -> arr[0], // key 映射
        arr -> arr[1]  // value 映射
    ));
// 若 key 可能重复,需指定冲突解决策略(如保留第一个)
Map<String, String> userMapSafe = userList.stream()
    .map(user -> user.split(":"))
    .collect(Collectors.toMap(
        arr -> arr[0],
        arr -> arr[1],
        (v1, v2) -> v1 // 冲突时保留第一个值
    ));

  ④ 聚合统计(求和、平均值等)

List<Integer> numList = Arrays.asList(1,2,3,4,5);
// 求和(Integer 类型)
int sum = numList.stream().collect(Collectors.summingInt(Integer::intValue));
// 求和(Long 类型,避免溢出)
long sumLong = numList.stream().collect(Collectors.summingLong(Integer::longValue));
// 平均值(返回 Double)
double avg = numList.stream().collect(Collectors.averagingInt(Integer::intValue));
// 统计信息(总和、平均值、计数等)
IntSummaryStatistics stats = numList.stream().collect(Collectors.summarizingInt(Integer::intValue));
System.out.println("总和:" + stats.getSum());
System.out.println("平均值:" + stats.getAverage());
System.out.println("最大值:" + stats.getMax());
  ⑤ 分组(groupingBy

  按指定条件分组,返回 Map<分组键, List<元素>>

List<String> fruitList = Arrays.asList("苹果", "香蕉", "橙子", "红苹果", "青苹果");
// 按首字符分组
Map<Character, List<String>> groupByFirstChar = fruitList.stream()
    .collect(Collectors.groupingBy(fruit -> fruit.charAt(0)));
// 结果:{苹=[苹果, 红苹果, 青苹果], 香=[香蕉], 橙=[橙子]}
// 分组后统计个数(返回 Map<分组键, 计数>)
Map<Character, Long> countByGroup = fruitList.stream()
    .collect(Collectors.groupingBy(
        fruit -> fruit.charAt(0),
        Collectors.counting() // 下游收集器:统计个数
    ));
// 结果:{苹=3, 香=1, 橙=1}

  ⑥ 拼接字符串(joining

List<String> strList = Arrays.asList("a", "b", "c");
String join1 = strList.stream().collect(Collectors.joining()); // "abc"
String join2 = strList.stream().collect(Collectors.joining(",")); // "a,b,c"
String join3 = strList.stream().collect(Collectors.joining(",", "[", "]")); // "[a,b,c]"

五、并行

Stream 天生支持并行,只需将串行流转为并行流,无需手动处理线程:

  1. 集合直接获取并行流:list.parallelStream()
  2. 串行流转为并行流:stream.parallel()

注意:

示例:

List<Integer> numList = IntStream.range(1, 1000000).boxed().collect(Collectors.toList());
// 串行流求和(耗时较长)
long serialSum = numList.stream().mapToLong(Integer::longValue).sum();
// 并行流求和(效率更高)
long parallelSum = numList.parallelStream().mapToLong(Integer::longValue).sum();

六、注意

  1. 忘记终端操作:只写中间操作不写终端操作,代码不会执行;
  2. 重复使用 Stream:一个 Stream 执行终端操作后关闭,再次使用会抛出 IllegalStateException
  3. 并行流的副作用:并行流中修改外部非线程安全的变量(如 ArrayList),会导致数据错乱;
  4. 滥用 peekpeek 设计用于调试,不建议用于修改元素(虽然语法允许);
  5. 忽略 Optional 空指针风险:maxfindFirst 等返回 Optional,直接调用 get() 可能抛出 NoSuchElementException,建议用 orElse() 或 ifPresent() 处理空值:
// 错误:无元素时抛出异常
Integer max = Stream.empty().max(Integer::compare).get();
// 正确:无元素时返回默认值 0
Integer maxSafe = Stream.empty().max(Integer::compare).orElse(0);

七、总结

  Stream 是 Java 处理集合数据的利器,特点总结:

  实际开发中,建议用 Stream 替代传统的 for 循环和 Iterator,尤其是处理复杂数据转换、过滤、统计时,能大幅提升开发效率和代码质量。

到此这篇关于Java中的Stream流式操作大全的文章就介绍到这了,更多相关java stream流内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文