如何在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)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
为什么Windows文件管理器无法显示隐藏文件?教你快速显示的技巧
上一篇 2025年11月25日 20:53:40
豆包AI神级案例!三天涨粉5w的影视梗图全解析
下一篇 2025年11月25日 20:53:46

相关推荐

  • 如何基于Swoole开发自定义框架?

    基于swoole开发自定义框架可以通过以下步骤实现:1. 创建核心app类,初始化swoole服务器并定义回调函数;2. 实现路由功能,使用router类处理请求分发;3. 添加中间件支持,使用middleware类处理请求;4. 集成异步数据库操作,使用swoole的mysql协程客户端;5. 实…

    2026年9月21日
    000
  • 万人同时在线抽奖活动架构

    万人同时在线抽奖活动的系统架构应采用微服务架构、分布式数据库、redis缓存、区块链存储结果,并使用负载均衡和异步处理技术。具体包括:1.采用微服务架构和分布式数据库(如tidb)保证系统稳定性和可扩展性;2.使用redis处理抽奖逻辑,确保高效和随机性;3.将结果存入区块链,保证透明度和可验证性;…

    2026年9月21日
    000
  • 自定义组件(Component)的开发方法

    开发自定义组件的步骤包括:1. 使用html和css定义组件结构和样式;2. 用javascript实现动态效果和状态管理;3. 确保跨浏览器和设备兼容性;4. 采用模块化设计和外部状态管理工具;5. 进行性能优化和测试驱动开发。通过这些步骤,可以创建出优雅且高效的自定义组件,提升用户体验。 在开发…

    2026年9月21日
    000
  • mysql如何排查磁盘IO瓶颈

    首先检查系统级磁盘IO,使用iostat、iotop等工具分析磁盘利用率和进程IO行为;再通过MySQL慢查询日志、sys.schema视图及SHOW ENGINE INNODB STATUS排查高IO消耗的SQL与内部等待事件;接着评估innodb_buffer_pool_size、innodb_…

    2026年9月21日
    000
  • Linux如何创建新用户并设置初始密码

    Linux如何创建新用户并设置初始密码Linux如何创建新用户并设置初始密码Linux如何创建新用户并设置初始密码Linux如何创建新用户并设置初始密码

    创建新用户并设初始密码需用useradd加passwd命令,如sudo useradd -m -s /bin/bash devuser创建用户,sudo passwd devuser设置密码;通过sudo usermod -aG sudo devuser赋予sudo权限;密码策略应包含长度、复杂度、…

    2026年9月21日 用户投稿
    100
  • Java中浮点数比较的陷阱:理解double类型的不精确性与正确比较方法

    java中`double`类型因其二进制浮点表示的固有不精确性,即使在相同java版本和架构下,也可能在不同环境中产生微小的数值差异。直接使用`==`比较浮点数是不可靠的,因为它无法容忍这些细微的舍入误差。正确的做法是采用基于容差(epsilon)的比较方法,通过判断两数之差的绝对值是否小于一个预设…

    2026年9月21日
    200
  • 如何避免协程中的共享资源竞争?

    避免协程中的共享资源竞争可以通过以下方法:1. 使用锁(locks),如互斥锁或读写锁,确保同一时间只有一个协程访问共享资源。2. 采用无锁数据结构(lock-free data structures),通过原子操作和cas操作提高并发性能。3. 实施消息传递(message passing),通过…

    2026年9月21日
    100
  • Jedis jsonGet 方法返回字节数组值末尾出现 .0 的处理策略

    当使用jedis客户端的`jsonget`方法从redis获取json数据时,如果其中包含字节数组(如xml字符串的字节表示),可能会因底层json库(如gson或org.json)的默认行为,导致数字被统一上转型为`double`类型,从而在输出中显示`.0`后缀。本文将深入探讨此问题产生的原因,…

    2026年9月21日
    300
  • Laravel与Vue.js/React前端框架集成

    laravel可以与vue.js或react集成。1) 使用命令“php artisan preset vue”或“php artisan preset react”设置开发环境。2) 在laravel视图中引入编译后的javascript文件。3) 通过laravel的api路由和前端框架的htt…

    2026年9月21日
    100
  • mysql索引的类型和作用有哪些

    MySQL常见索引类型包括:1. 普通索引用于加速查询;2. 唯一索引确保列值唯一;3. 主键索引为唯一非空且自动创建聚簇索引;4. 聚簇索引决定数据物理存储顺序,每表仅一个;5. 非聚簇索引保存主键值,需回表查询;6. 覆盖索引避免回表提升性能;7. 联合索引遵循最左前缀原则;8. 全文索引支持文…

    2026年9月21日
    100
  • 在Java中静态方法能否被重写

    静态方法属于类而非实例,不参与运行时动态绑定,因此不能被重写;2. 子类定义同名静态方法时发生方法隐藏,调用时机由引用类型在编译阶段决定;3. 如示例所示,Parent p = new Child() 调用 p.display() 输出 “Parent static method&#82…

    2026年9月21日
    100
  • 在Java中变量和常量有什么区别

    变量的值可修改,常量(用final修饰)一旦赋值不可变;变量用于动态数据,常量用于固定值,如PI或配置参数。 在Java中,变量和常量的主要区别在于它们的值能否被修改。变量的值可以在程序运行过程中改变,而常量一旦赋值就不能再更改。 变量(Variable) 变量是用于存储数据的基本单元,其值在程序执…

    2026年9月21日
    200
  • 在Java中如何使用方法重载

    方法重载允许类中多个同名方法共存,只要参数列表不同即可。例如Calculator类中add方法可接受不同数量、类型或顺序的参数,Java根据传入参数自动匹配对应方法,提升调用灵活性与代码可读性。 方法重载(Overloading)是Java中实现多态的一种方式,它允许在一个类中定义多个同名方法,只要…

    2026年9月21日
    200
  • VSCode的括号着色功能如何帮助你避免语法错误?

    VSCode括号着色功能通过彩色高亮匹配括号,帮助用户直观识别嵌套结构、提升代码可读性,并快速发现遗漏或多余括号,减少语法错误。 VSCode的括号着色功能通过视觉方式帮你快速识别代码中的匹配和嵌套结构,减少语法错误的发生。当你在编写代码时,成对出现的括号(如()、[]、{})会被高亮显示为相同或相…

    2026年9月21日
    000
  • Java中如何将嵌套列表对象转换为扁平化单元素列表

    本文探讨了在java中将包含嵌套列表的对象集合转换为新列表的多种策略,旨在使新列表中每个对象仅包含其嵌套列表中的一个元素。通过详细介绍java 7的传统迭代方法、java 8-15的stream api `flatmap`操作,以及java 16及更高版本的`mapmulti`方法,文章提供了清晰的…

    2026年9月21日
    100
  • Linux如何查看sudo执行的历史记录

    Linux如何查看sudo执行的历史记录Linux如何查看sudo执行的历史记录Linux如何查看sudo执行的历史记录Linux如何查看sudo执行的历史记录

    要追溯sudo执行的命令,需查看系统日志或配置sudo日志;在Ubuntu/Debian中查/var/log/auth.log,CentOS/RHEL中查/var/log/secure,或使用journalctl _COMM=sudo筛选;通过配置/etc/sudoers中的Defaults log…

    2026年9月21日 用户投稿
    300
  • 如何模拟用户登录状态进行测试?

    模拟用户登录状态是为了测试系统功能和安全性。1.在开发初期帮助发现和修复问题。2.测试不同用户权限下的功能访问。方法包括:1.直接操作session或cookie。2.使用测试框架如junit或testng。3.模拟api请求。 模拟用户登录状态进行测试是确保软件系统用户体验和安全性的关键步骤。无论…

    2026年9月21日
    300
  • REDMI有史以来最强手机!K90 Pro Max这次真的强到爆

    REDMI有史以来最强手机!K90 Pro Max这次真的强到爆REDMI有史以来最强手机!K90 Pro Max这次真的强到爆REDMI有史以来最强手机!K90 Pro Max这次真的强到爆REDMI有史以来最强手机!K90 Pro Max这次真的强到爆

    如果说redmi过去是“性价比之王”,那么这一次,它彻底进化成了“性能怪兽”。10月23日即将登场的redmi k90 pro max,不仅是品牌年度旗舰的压轴大戏,更是其历史上首款冠以“pro max”之名的巅峰之作。 这可以看作是REDMI向高端市场发起冲击的正式宣言。卢伟冰亲自放话:“给4K价…

    2026年9月21日 用户投稿
    300
  • Linux如何升级源码编译安装的软件

    Linux如何升级源码编译安装的软件Linux如何升级源码编译安装的软件Linux如何升级源码编译安装的软件Linux如何升级源码编译安装的软件

    升级源码编译软件需手动操作,核心是卸载旧版后编译安装新版。1. 确认当前版本、路径及编译参数;2. 下载并解压新版本源码,核对完整性;3. 使用相同配置参数执行 ./configure 和 make 编译;4. 停止旧服务,执行 make install 覆盖文件;5. 验证版本与配置,启动服务并检…

    2026年9月21日 用户投稿
    100
  • Valkey 9.0.0 正式 GA,Linux 基金会维护的 Redis 分支

    由 linux 基金会主导维护的 redis 分支——开源内存数据库 valkey 9.0.0 正式发布,现已进入通用可用(ga)阶段。本次版本带来了多项关键性的性能提升与功能增强,重点聚焦于更高效的集群数据迁移、精细化的哈希字段管理能力,以及对大规模分布式部署场景的深度优化。 原子化槽位迁移(At…

    2026年9月21日
    100

发表回复

登录后才能评论
关注微信