如何在Java中使用Stream流 Java Stream常见用法解析

java中的stream流通过声明式风格简化了集合数据处理,其核心步骤为:1.创建stream;2.应用中间操作;3.执行终端操作。创建stream常见方式包括从集合或数组获取,如list.stream()或arrays.stream()。中间操作如filter、map、flatmap实现数据转换与处理,且具备惰性求值特性,仅在终端操作触发时执行。终端操作如collect、foreach、reduce用于生成结果或副作用,且stream只能被消费一次。相比传统循环,stream提升了代码可读性与维护性,并通过惰性求值和短路操作优化性能,尤其适用于大数据量场景。使用时需注意stream不可重复使用、peek用于调试而非修改元素、optional安全处理、并行流合理应用及调试技巧等最佳实践,以确保高效可靠的数据处理。

如何在Java中使用Stream流 Java Stream常见用法解析

Java中的Stream流,在我看来,它彻底改变了我们处理集合数据的方式,从传统的命令式循环转向了一种更声明式、更函数式的风格。它提供了一套强大的API,让我们能以一种非常简洁且高效的方式对数据进行过滤、映射、聚合等操作。简单讲,它就是处理数据序列的利器,让代码读起来更像是在描述“要做什么”,而不是“怎么去做”。

如何在Java中使用Stream流 Java Stream常见用法解析

解决方案

要使用Java Stream,通常会经历几个步骤:创建Stream、应用零个或多个中间操作、最后执行一个终端操作。

创建Stream:这通常是开始的第一步。我最常用的是从集合(如ListSet)或数组中获取Stream。

如何在Java中使用Stream流 Java Stream常见用法解析

import java.util.Arrays;import java.util.List;import java.util.Set;import java.util.stream.Collectors;import java.util.stream.Stream;import java.util.Optional;// 从List创建List names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Alice");Stream nameStream = names.stream();// 从数组创建String[] cities = {"New York", "London", "Paris"};Stream cityStream = Arrays.stream(cities);// 直接创建Stream numbers = Stream.of(1, 2, 3, 4, 5);// 也可以通过Stream.builder()、Stream.generate()等方式创建,但日常开发中前几种更常见。

中间操作:这些操作会返回一个新的Stream,它们是“惰性”的,也就是说,只有当终端操作被调用时,它们才会真正执行。

filter(Predicate predicate) 根据条件筛选元素。

立即学习“Java免费学习笔记(深入)”;

如何在Java中使用Stream流 Java Stream常见用法解析

List filteredNames = names.stream()                                  .filter(name -> name.startsWith("A"))                                  .collect(Collectors.toList()); // ["Alice", "Alice"]

map(Function mapper) 将Stream中的每个元素转换成另一种类型。

List nameLengths = names.stream()                                 .map(String::length) // 或者 name -> name.length()                                 .collect(Collectors.toList()); // [5, 3, 7, 5, 5]

flatMap(Function<T, Stream> mapper) 当你的映射函数返回一个Stream时,flatMap会把所有子Stream连接成一个扁平的Stream。这在处理嵌套结构时特别有用。

List<List> listOfLists = Arrays.asList(    Arrays.asList("a", "b"),    Arrays.asList("c", "d"));List flatList = listOfLists.stream()                                   .flatMap(List::stream)                                   .collect(Collectors.toList()); // ["a", "b", "c", "d"]

distinct() 去除重复元素。

List uniqueNames = names.stream()                                .distinct()                                .collect(Collectors.toList()); // ["Alice", "Bob", "Charlie", "David"]

sorted() / sorted(Comparator comparator) 排序。

List sortedNames = names.stream()                                .sorted() // 自然排序                                .collect(Collectors.toList()); // ["Alice", "Alice", "Bob", "Charlie", "David"]List customSortedNames = names.stream()                                      .sorted((n1, n2) -> Integer.compare(n1.length(), n2.length())) // 按长度排序                                      .collect(Collectors.toList());

limit(long maxSize) / skip(long n) 截取或跳过元素。

List firstTwoNames = names.stream().limit(2).collect(Collectors.toList()); // ["Alice", "Bob"]List skipFirstTwo = names.stream().skip(2).collect(Collectors.toList()); // ["Charlie", "David", "Alice"]

peek(Consumer action) 对Stream中的每个元素执行一个操作,但不改变Stream本身。主要用于调试。

List processedNames = names.stream()                                   .filter(name -> name.length() > 3)                                   .peek(name -> System.out.println("Processing: " + name)) // 调试输出                                   .map(String::toUpperCase)                                   .collect(Collectors.toList());

终端操作:这些操作会消耗Stream,产生一个结果(例如一个集合、一个值或一个副作用)。Stream一旦被消耗就不能再次使用。

forEach(Consumer action) 对Stream中的每个元素执行一个操作。

names.stream().forEach(System.out::println);

collect(Collector collector) 将Stream中的元素收集到一个集合或其他数据结构中。这是我用得最多的一个。

List toList = names.stream().collect(Collectors.toList());Set toSet = names.stream().collect(Collectors.toSet());String joinedNames = names.stream().collect(Collectors.joining(", ")); // "Alice, Bob, Charlie, David, Alice"// 收集到Map,注意键重复的处理// Map nameToLength = names.stream()//                                       .collect(Collectors.toMap(name -> name, String::length)); // 可能有Duplicate key错误Map nameToLengthSafe = names.stream()                                          .collect(Collectors.toMap(name -> name, String::length, (oldValue, newValue) -> oldValue)); // 解决重复键冲突

reduce(BinaryOperator accumulator) / reduce(T identity, BinaryOperator accumulator) 将Stream中的元素组合成一个单一的结果。

Optional combined = names.stream().reduce((s1, s2) -> s1 + "-" + s2); // "Alice-Bob-Charlie-David-Alice" (Optional)int sumLengths = names.stream().mapToInt(String::length).reduce(0, Integer::sum); // 25

min(Comparator comparator) / max(Comparator comparator) 查找最小值或最大值。

Optional longestName = names.stream().max(Comparator.comparing(String::length)); // Optional["Charlie"]

count() 返回Stream中的元素数量。

long count = names.stream().count(); // 5

anyMatch(Predicate predicate) / allMatch(Predicate predicate) / noneMatch(Predicate predicate) 检查Stream中的元素是否满足某个条件。这些是短路操作。

boolean hasLongName = names.stream().anyMatch(name -> name.length() > 6); // trueboolean allShortNames = names.stream().allMatch(name -> name.length() < 10); // true

findFirst() / findAny() 查找Stream中的第一个或任意一个元素。返回Optional

Optional first = names.stream().findFirst(); // Optional["Alice"]Optional any = names.stream().findAny(); // 可能是Optional["Alice"],并行流下可能是其他

Stream流与传统循环:效率与可读性对比

我个人在使用Stream流时,最直观的感受就是代码的“意图”变得更清晰了。对比传统forwhile循环,Stream流的链式调用和函数式风格,让我能更专注于“做什么”而不是“怎么做”。

比如说,如果你想从一个列表里找出所有长度大于5的名字,然后把它们转换成大写,最后收集起来:

传统循环写法:

List originalNames = Arrays.asList("Alice", "Bob", "Charlie", "David");List result = new ArrayList();for (String name : originalNames) {    if (name.length() > 5) {        result.add(name.toUpperCase());    }}// 结果: ["CHARLIE"]

这段代码,嗯,很直接,一步一步地告诉机器该怎么做。但当逻辑变得复杂,比如再加个排序、去重什么的,循环内部就会变得越来越臃肿,嵌套也可能越来越多,读起来就有点头疼了。

Stream流写法:

List originalNames = Arrays.asList("Alice", "Bob", "Charlie", "David");List result = originalNames.stream()                                   .filter(name -> name.length() > 5)                                   .map(String::toUpperCase)                                   .collect(Collectors.toList());// 结果: ["CHARLIE"]

你看,Stream流的写法就像是在描述一个数据处理的“管道”:数据先进来,经过过滤,再经过映射,最后被收集出去。每个操作都是一个独立的步骤,非常清晰。这在团队协作时尤其重要,大家能更快地理解代码逻辑。

Android数据格式解析对象JSON用法 WORD版 Android数据格式解析对象JSON用法 WORD版

本文档主要讲述的是Android数据格式解析对象JSON用法;JSON可以将Java对象转成json格式的字符串,可以将json字符串转换成Java。比XML更轻量级,Json使用起来比较轻便和简单。JSON数据格式,在Android中被广泛运用于客户端和服务器通信,在网络数据传输与解析时非常方便。希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看

Android数据格式解析对象JSON用法 WORD版 0 查看详情 Android数据格式解析对象JSON用法 WORD版

至于效率,很多人会问Stream流是不是一定比传统循环快。我的经验是,对于小规模数据,两者性能差异不大,甚至传统循环可能因为更直接的内存访问而略快一点点。但Stream流在处理大规模数据时,尤其是结合parallelStream()使用时,能很方便地利用多核CPU进行并行处理,从而显著提升性能。当然,并行流也不是万能药,它有自己的开销,不是所有场景都适合。但至少,它提供了一个简单的并行化途径,这是传统循环很难直接做到的。所以,更多时候,我选择Stream流是出于代码可读性和维护性的考量,性能提升则是一个额外的、很不错的优势。

Stream流的惰性求值与短路操作

Stream流的一个核心特性就是它的“惰性求值”(Lazy Evaluation)。这意味着,当你调用像filter()map()这样的中间操作时,它们并不会立即执行计算,而只是构建了一个操作链。真正的计算发生在终端操作(如collect()forEach()count()等)被调用时。这听起来有点抽象,但它对性能优化至关重要。

举个例子,假设我们有一个很大的数字列表,我们想找到第一个偶数:

List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);Optional firstEven = numbers.stream()                                     .filter(n -> {                                         System.out.println("Filtering: " + n); // 观察执行顺序                                         return n % 2 == 0;                                     })                                     .findFirst(); // 终端操作System.out.println("First even number: " + firstEven.orElse(-1));

运行这段代码,你会发现输出是这样的:

Filtering: 1Filtering: 2First even number: 2

注意到没?它只过滤了1和2,一旦找到第一个偶数2,findFirst()这个终端操作就“短路”了,后续的元素(3到10)根本没有被filter方法处理。这就是惰性求值和“短路操作”的威力。

短路操作(Short-Circuiting Operations)是Stream API中一类特殊的终端或中间操作,它们在处理完部分元素后就能得到结果,从而无需处理整个Stream。常见的短路操作包括:

终端操作: findFirst(), findAny(), anyMatch(), allMatch(), noneMatch()中间操作: limit()

理解这一点非常重要,它能帮助我们写出更高效的代码。比如,如果你只是想检查列表中是否存在满足某个条件的元素,用anyMatch()就比先filtercount(或者collect)要高效得多,因为anyMatch一旦找到匹配项就会立即停止。这种设计思想让Stream流在处理大数据量时显得非常灵活和高效。

处理Stream流中的常见陷阱与最佳实践

在使用Stream流的过程中,我确实遇到过一些“坑”,也总结了一些经验。

1. Stream不能重复使用:这是最常见的一个陷阱。Stream一旦执行了终端操作,就被“消费”掉了,不能再次使用。如果你尝试这样做,会抛出IllegalStateException: stream has already been operated upon or closed

Stream myStream = names.stream();myStream.forEach(System.out::println); // 第一次使用,Stream被消费// myStream.filter(name -> name.startsWith("A")).collect(Collectors.toList()); // 再次使用,会报错!

最佳实践: 如果你需要对同一个数据源执行多个Stream操作,每次都应该重新创建一个Stream。

2. peek()的正确使用:peek()是一个中间操作,它允许你在Stream的每个元素经过时执行一个副作用操作。很多人会误以为peek()可以用来改变Stream中的元素,或者作为终端操作。但它的主要目的是调试,或者在不中断Stream链的情况下观察中间结果。

// 错误用法示例:试图用peek改变元素,但没有后续终端操作,或者没理解其副作用性质names.stream()     .peek(name -> name.toUpperCase()); // 这个操作不会生效,因为没有终端操作,且peek不改变元素

最佳实践: peek()应该用于无状态的、不改变Stream元素的操作,比如日志记录。如果需要改变元素,请使用map()。记住,peek()之后必须跟一个终端操作,它才会被执行。

3. Optional的处理:min()max()findFirst()findAny()以及reduce()(无初始值)这些操作返回的是Optional类型。这是因为在Stream为空时,它们可能没有结果。直接调用get()方法在Optional为空时会抛出NoSuchElementException

List emptyList = new ArrayList();Optional first = emptyList.stream().findFirst();// String value = first.get(); // 危险!如果first为空会报错Optional maxNum = Stream.empty().max(Integer::compare);// int val = maxNum.get(); // 同样危险

最佳实践: 始终使用Optional提供的方法来安全地获取值,例如orElse(), orElseGet(), orElseThrow(), ifPresent(), 或者isPresent()结合get()(但在确保isPresent()为true之后)。

String value = first.orElse("Default Value");first.ifPresent(v -> System.out.println("Found: " + v));

4. 并行流的滥用:parallelStream()能显著提升大数据量下的处理速度,但它并非总是最佳选择。并行流引入了线程管理的开销,对于小数据量或者I/O密集型操作(而非CPU密集型)来说,并行处理的开销可能比串行处理更大,导致性能反而下降。

最佳实践: 只有在数据量大、操作是CPU密集型且能够被有效并行化时,才考虑使用parallelStream()。同时,要注意并行流可能导致的顺序问题(除非你使用forEachOrdered()等)和共享可变状态的问题。通常,先用串行流实现,如果性能瓶颈出现在Stream操作上,再考虑优化为并行流。

5. 调试Stream:Stream的链式调用虽然优雅,但在出现问题时调试起来可能不如传统循环直观。

最佳实践: 使用peek()操作插入日志,观察Stream在每个阶段的数据变化。或者,将复杂的Stream链拆分成多个小的Stream,逐步调试。IDE(如IntelliJ IDEA)的调试器也对Stream提供了很好的支持,可以一步步查看中间结果。

掌握这些,能让你在Java Stream的路上走得更稳健。

以上就是如何在Java中使用Stream流 Java Stream常见用法解析的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/752090.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月25日 20:48:04
下一篇 2025年11月25日 20:53:55

相关推荐

发表回复

登录后才能评论
关注微信