Java Stream 流常用方法

在 Java 8 引入的众多特性中,Stream API 无疑是最能提升代码优雅度和开发效率的工具之一。它让我们能以声明式的方式处理数据集合(类似于 SQL 语句),告别了繁琐的 for 循环和复杂的逻辑判断。

本文将基于实战角度,带你全面掌握 Stream 流的创建中间操作终结操作以及方法引用的高级用法。


一、 不可变集合

在开始学习 Stream 之前,了解 JDK 9+ 引入的不可变集合工厂方法非常有用,它们常被用来快速创建测试数据。

  • List 和 SetList.of()Set.of(),形参依次传入 value。
  • MapMap.of(),形参依次传入 key 和 value。

Java

// 示例 List<String> list = List.of("张三", "李四", "王五"); Set<String> set = Set.of("A", "B", "C"); Map<String, Integer> map = Map.of("张三", 18, "李四", 20); 

二、 第一步:获取 Stream 流

Stream 流的生命周期始于“数据源”。不同的数据结构获取流的方式略有不同。

1. 单列集合 (List, Set)

直接调用集合的 .stream() 方法。

Java

ArrayList<String> list = new ArrayList<>(); Stream<String> stream = list.stream(); 

2. 双列集合 (Map)

Map 本身不能直接获取流,需要先将其转换为单列集合(KeySet 或 EntrySet)后再获取。

Java

HashMap<String, Integer> map = new HashMap<>(); // 1. 对键获取流 Stream<String> keyStream = map.keySet().stream(); // 2. 对键值对获取流 (常用) Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream(); 

3. 数组

使用 Arrays 工具类的静态方法。

Java

int[] arr = {1, 2, 3}; IntStream stream = Arrays.stream(arr); 

4. 零散数据

使用 Stream.of() 静态方法。

注意:如果传入的是基本数据类型的数组(如 int[]),Stream.of 会把整个数组当成一个元素(即传递的是地址);引用类型数组则会正常展开。

Java

Stream<Integer> stream = Stream.of(1, 2, 3, 4); 

三、 第二步:Stream 中间方法 (Intermediate Operations)

中间方法具有惰性求值的特点:它们不会立即执行,只有当遇到“终结方法”时,整个流水线才会启动。

核心规则:Stream 流只能使用一次,复用会报错,建议使用链式编程。中间操作不会修改原集合的数据。

1. 筛选与切片

  • limit(n):截断流,只取前 n 个数据。
  • skip(n):跳过流,扔掉前 n 个数据。

distinct():元素去重。

注意:该方法依赖元素的 hashCode()equals() 方法。如果是自定义对象,请务必重写这两个方法。

filter(Predicate):过滤。返回 false 代表当前数据舍弃,返回 true 代表保留。Java

// 留下 list 中以“张”开头的数据 list.stream().filter(ele -> ele.startsWith("张")); 

2. 组合 (Concat)

  • Stream.concat(a, b):将两个流合并为一个流。

3. 映射 (Map & FlatMap)

flatMap(Function):扁平化映射。主要作用是将一个流中的每个元素映射成一个新的流,然后将这些新的流合并成一个单一的流。Java

// 示例:将嵌套列表 [[1,2], [3,4], [5,6]] 展平为 [1,2,3,4,5,6] List<List<Integer>> listOfLists = Arrays.asList( Arrays.asList(1, 2), Arrays.asList(3, 4), Arrays.asList(5, 6) ); List<Integer> flattenedList = listOfLists.stream() .flatMap(List::stream) // 将每个小 List 变成 Stream .collect(Collectors.toList()); 

map(Function):转换流中的数据类型。Java

// 示例:将 "张三-20" 这种字符串转为 int 类型的年龄 List<String> list = Arrays.asList("张三-20", "李四-18"); list.stream() .map(s -> Integer.parseInt(s.split("-")[1])) // 字符串转数字 .forEach(System.out::println); 

4. 排序 (Sorted)

  • sorted():根据自然顺序排序(要求元素实现 Comparable 接口)。

sorted(Comparator):通过自定义比较器排序。升序示例:Java

// 先按年龄排序,年龄相同则按名字排序 List<Person> sortedList = people.stream() .sorted(Comparator.comparing(Person::getAge) .thenComparing(Person::getName)) .collect(Collectors.toList()); 

降序示例: 可以在 comparing 后面链式调用 .reversed(),或者直接使用 Comparator.reverseOrder()。Java

// 按年龄降序 people.stream().sorted(Comparator.comparing(Person::getAge).reversed()); 

5. 调试 (Peek)

  • peek(Consumer):类似于 forEach,但它是一个中间操作。通常用于在流的各个阶段打印日志,观察数据流向,而不打断流的处理。

四、 第三步:Stream 终结方法 (Terminal Operations)

终结方法是流操作的最后一步,调用后流即关闭,无法再次使用。

1. 遍历与统计

  • count():返回流中数据的个数(long 类型)。

forEach(Consumer):遍历操作,返回值为 void。Java

stream.forEach(ele -> System.out.println(ele)); 

2. 查找与匹配

  • anyMatch(Predicate):判断流中是否包含任意一个满足条件的元素。
  • allMatch(Predicate):判断流中是否所有元素都满足条件。
  • findFirst():返回流中的第一个元素(返回类型为 Optional)。

3. 数组转换

    • 不传参:返回 Object[]
    • 传参:返回指定类型的数组。

toArray():Java

// lambda 表达式中的 value 代表数组的长度 String[] arr = stream.toArray(value -> new String[value]); // 或者使用方法引用 String[] arr2 = stream.toArray(String[]::new); 

4. 归约 (Reduce)

reduce():用于将流中的所有元素结合起来,得到一个值。例如求和、求最大值等。Java

// 计算 1-10 的和 Integer sum = Stream.iterate(1, x -> x + 1).limit(10) .reduce(0, (a, b) -> a + b); 

5. 收集 (Collect)

这是最常用的终结方法,将流转变为集合或其他数据结构。

  • Collectors.toList() / toSet():收集到 List 或 Set 中。

Collectors.groupingBy()分组(高频用法)。 根据某个分类函数对流中的元素进行分组,返回一个 Map。场景 1:基础分组Java

// 按照年龄分组,返回 Map<Integer, List<Person>> Map<Integer, List<Person>> peopleByAge = people.stream() .collect(Collectors.groupingBy(Person::getAge)); 

场景 2:分组并统计Java

// 按照年龄分组,并计算每个年龄段的人数,返回 Map<Integer, Long> Map<Integer, Long> peopleCountByAge = people.stream() .collect(Collectors.groupingBy(Person::getAge, Collectors.counting())); 

Collectors.toMap():收集到 Map 中。

注意:toMap 如果遇到重复的 Key 会报错,建议处理 Key 冲突。

示例:将 "名字-性别-年龄" 格式的数据转为 Map,以名字为 Key,年龄为 Value。Java

List<String> list = Arrays.asList("张三-男-20", "李四-女-18"); Map<String, Integer> map = list.stream() .collect(Collectors.toMap( s -> s.split("-")[0], // Key: 名字 s -> Integer.parseInt(s.split("-")[2]) // Value: 年龄 )); 

五、 进阶:方法引用 (Method References)

当 Lambda 表达式体中仅仅是调用一个已存在的方法时,可以使用方法引用 :: 来简化代码,使代码更具可读性。

1. 静态方法引用

格式:ClassName::staticMethod

Java

// Lambda: s -> Integer.parseInt(s) // 引用: Integer::parseInt list.stream().map(Integer::parseInt).collect(Collectors.toList()); 

2. 实例方法引用

格式:object::instanceMethod (对象名::成员方法)

Java

StringBuilder sb = new StringBuilder(); // Lambda: s -> sb.append(s) // 引用: sb::append list.stream().forEach(sb::append); 

3. 特定类型的任意对象的实例方法引用

格式:ClassName::method适用场景:Lambda 的第一个参数是方法的调用者,后面的参数是方法的形参。

Java

List<String> words = Arrays.asList("apple", "banana"); // Lambda: (s) -> s.length() --> 相当于调用 "apple".length() // 引用: String::length List<Integer> lengths = words.stream() .map(String::length) .collect(Collectors.toList()); 

4. 构造方法引用

格式:ClassName::new

Java

List<String> names = Arrays.asList("Alice", "Bob"); // Lambda: name -> new Person(name) // 引用: Person::new List<Person> people = names.stream() .map(Person::new) .collect(Collectors.toList()); 

六、 总结

Java Stream API 的核心流程可以概括为: 数据源 (Source) -> 中间操作 (Intermediate) -> 终结操作 (Terminal)

Read more

告别996:GitHub Copilot将我的开发效率提升300%的实战记录

告别996:GitHub Copilot将我的开发效率提升300%的实战记录

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕AI这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * 告别996:GitHub Copilot将我的开发效率提升300%的实战记录 * 引言:从疲惫到高效 * 什么是GitHub Copilot?🤖 * 效率提升300%的核心场景 * 1. 快速生成样板代码 * 2. 自动编写单元测试 * 3. 智能调试与注释 * 集成Copilot到工作流 * 步骤1:设置合理的期望 * 步骤2:结合IDE使用 * 步骤3:代码审查与调整 * 高级用法:超越代码生成 * 数据库查询优化 * API接口设计 * 正则表达式助手 * 数据支撑:效率提升分析 * 避坑指南:常见问题与解决 * 1. 可能生成过时或不安全代码

By Ne0inhk

睡前定方向,醒来收初稿:全自动跑实验改论文的工作流开源了

与其在实验室通宵,不如让 Claude 替你卷。 如果你还在熬夜手搓代码、调参跑实验,那这个刚刚开源的科研工作流绝对会让你眼前一亮。 它就是 ARIS(Auto-Research-In-Sleep),一款真正帮你实现“睡后科研”的全自动神器。 这个项目的核心理念很直接,让 Claude Code 在你睡觉时做科研。 睡前丢给 AI 一篇论文初稿,醒来就能发现,站不住脚的 claim 已被剔除,20 多组 GPU 实验默默跑完,整篇论文的叙事框架焕然一新,分数也从 5.0 稳步提升到了可投稿的 7.5 分——而且全流程零人工干预。 作为一套专为机器学习科研定制的 Claude Code Skills,ARIS 既吸收了 FARS 的经验,也呼应了 Karpathy 提出的 autoresearch

By Ne0inhk
内置AI与浏览器的开源终端Wave Terminal安装与远程连接内网服务器教程

内置AI与浏览器的开源终端Wave Terminal安装与远程连接内网服务器教程

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老导航檀越剑指大厂系列:全面总结 java 核心技术,jvm,并发编程 redis,kafka,Spring,微服务等常用开发工具系列:常用的开发工具,IDEA,Mac,Alfred,Git,typora 等数据库系列:详细总结了常用数据库 mysql 技术点,以及工作中遇到的 mysql 问题等新空间代码工作室:提供各种软件服务,承接各种毕业设计,毕业论文等懒人运维系列:总结好用的命令,解放双手不香吗?能用一个命令完成绝不用两个操作数据结构与算法系列:总结数据结构和算法,不同类型针对性训练,提升编程思维,剑指大厂 非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。💝💝💝 ✨✨ 欢迎订阅本专栏 ✨✨ 博客目录 * 前言

By Ne0inhk
TWIST2——全身VR遥操控制:采集人形全身数据后,可训练视觉base的自主策略(基于视觉观测预测全身关节位置)

TWIST2——全身VR遥操控制:采集人形全身数据后,可训练视觉base的自主策略(基于视觉观测预测全身关节位置)

前言 我司内部在让机器人做一些行走-操作任务时,不可避免的需要全身遥操机器人采集一些任务数据,而对于全身摇操控制,目前看起来效果比较好的,并不多 * 之前有个CLONE(之前本博客内也解读过),但他们尚未完全开源 * 于此,便关注到了本文要解读的TWIST2,其核心创新是:无动捕下的全身控制 PS,如果你也在做loco-mani相关的工作,欢迎私我你的一两句简介,邀你加入『七月:人形loco-mani(行走-操作)』交流群 第一部分 TWIST2:可扩展、可移植且全面的人形数据采集系统 1.1 引言与相关工作 1.1.1 引言 如TWIST2原论文所说,现有的人形机器人远程操作系统主要分为三大类: 全身控制,直接跟踪人体姿态,包括手臂、躯干和腿部在内的所有关节以统一方式进行控制(如 HumanPlus [12],TWIST [1] ———— TWIST的介绍详见此文《TWIST——基于动捕的全身遥操模仿学习:教师策略RL训练,学生策略结合RL和BC联合优化(可训练搬箱子)》 部分全身控制,

By Ne0inhk