当前位置: 首页 > news >正文

【JAVA】Java8的 Stream相关学习分析总结

 Stream 操作按 “核心需求场景” 分类归纳,每个类别下整合 “操作目的、核心方法、案例代码、关键说明”

四个基本语法概念:

Stream:的操作是链式执行的,每个操作都会基于上一步的结果生成新的流。
map:将流中的每个元素按照指定的规则(函数)进行转换,生成一个包含转换后元素的新流。
collect() 方法是一个终端操作, “执行收集动作” 的入口,而具体 “收集成什么样子”,由传入的 Collector(收集器)决定。

另一篇文章的补充(例子更清晰一些):https://blog.csdn.net/ew45218/article/details/144903267

// 创建一个学生列表
List<Student> students = Arrays.asList(new Student("Alice", 22, "Computer Science"),new Student("Bob", 21, "Mathematics"),new Student("Charlie", 23, "Chemistry"),new Student("David", 19, "Biology"),new Student("Eva", 20, "Mathematics"),new Student("Frank", 22, "History"),new Student("Grace", 21, "Biology")
);

一、基础筛选:从列表中 “挑出” 符合条件的元素

核心需求:过滤元素、判断元素是否存在(不改变元素本身,只筛选保留 / 判断)关键方法filter(过滤元素,中间操作)、anyMatch/allMatch/noneMatch(判断匹配,终端操作)

操作目的案例代码关键说明
过滤符合条件的对象List<Student> collect = students.stream().filter(s -> s.age > 20).collect(Collectors.toList());1. filter 是中间操作,需搭配 collect 终端操作才会执行;2. 筛选逻辑通过 Lambda 定义(这里是 “年龄> 20”)。
判断是否存在符合条件元素boolean hasUnderage = students.stream().anyMatch(s -> s.age < 18);1. anyMatch 是终端操作,直接返回 boolean;2. 短路操作:找到第一个符合条件的元素就停止,效率高。

二、元素转换:改变元素的 “形式” 或 “内容”

核心需求:提取对象属性、修改属性内容(将流中元素转换为新形式,生成新流)关键方法map(元素转换,中间操作)

操作目的案例代码关键说明
提取对象的某个属性List<String> collect3 = students.stream().map(Student::getMajor).distinct().collect(Collectors.toList());1. map(Student::getMajor) 将 Student 对象转为 String 类型的专业;2. 转换后流的元素类型从 Student 变为 String
修改属性内容List<String> collect1 = students.stream().map(s -> s.getName().toUpperCase()).collect(Collectors.toList());1. 这里将 “学生姓名” 转换为大写;2. 转换逻辑可自定义(如拼接字符串、数值计算等)。

三、排序:对列表元素按规则 “排顺序”

核心需求:按对象属性升序 / 降序排列关键方法sorted(排序,中间操作)+ Comparator(比较器)

操作目的案例代码关键说明
按属性倒序排序List<Student> collect2 = students.stream().sorted(Comparator.comparingInt(Student::getAge).reversed()).collect(Collectors.toList());1. Comparator.comparingInt(Student::getAge) 定义 “按年龄正序”;2. reversed() 反转顺序,实现倒序;3. 排序后仍保留完整 Student 对象。

四、统计计算:对数值类属性做 “聚合计算”

核心需求:求和、求平均、计数(针对数值型数据,得到单一结果)关键方法mapToInt(转为数值流,中间操作)、sum/average(终端操作)、reduce(通用聚合,终端操作)

操作目的案例代码关键说明
求数值总和(简单场景)int sum = students.stream().mapToInt(Student::getAge).sum();1. mapToInt 转为 IntStream,避免装箱 / 拆箱,性能高;2. sum() 是 IntStream 专用终端操作,直接返回总和。
求数值总和(自定义场景)int totalAge = students.stream().map(Student::getAge).reduce(0, (a, b) -> a + b);1. reduce 是通用聚合方法,适合复杂逻辑(如带条件的累加);2. 第一个参数是初始值(求和初始为 0),第二个是累加器(a是当前和,b是下一个元素)。
求平均值OptionalDouble average = students.stream().mapToInt(Student::getAge).average();1. 返回 OptionalDouble,避免流为空时返回 null;2. 可通过 average.getAsDouble() 获取具体值(需确保流非空)。
基础计数(列表大小)int size = students.size();1. 直接调用 List 的 size() 方法,适合无需 Stream 其他操作的简单计数;2. 若需先过滤再计数,可用 students.stream().filter(...).count()

五、去重:消除 “重复” 元素(按属性 / 对象)

核心需求:按属性去重(保留属性列表)、按属性去重(保留原始对象)关键方法distinct(简单去重,中间操作)、Collectors.toMap(按属性去重,终端操作)

操作目的案例代码关键说明
按属性去重(得到属性列表)List<String> collect3 = students.stream().map(Student::getMajor).distinct().collect(Collectors.toList());1. 先通过 map 提取专业属性,再用 distinct 去重;2. 最终得到 “无重复的专业列表”(List<String>)。
按属性去重(保留原始对象)List<Student> uniqueByMajor = students.stream().collect(Collectors.toMap(Student::getMajor, s->s, (e,n)->e)).values().stream().collect(Collectors.toList());1. 利用 Map 的 key 唯一特性:key 是专业,value 是 Student 对象;2. 第三个参数 (e,n)->e 表示 “重复时保留旧对象”;3. 最后通过 values() 提取去重后的 Student 对象。

六、分组与聚合:按规则 “归类” 元素,或合并元素

核心需求:按属性分组、合并元素(如拼接字符串)关键方法Collectors.groupingBy(分组,终端操作)、Collectors.joining(拼接,终端操作)、reduce(合并,终端操作)

操作目的案例代码关键说明
按属性分组Map<String, List<Student>> collect5 = students.stream().collect(Collectors.groupingBy(Student::getMajor));1. 按 “专业” 分组,key 是专业,value 是该专业的所有 Student 对象;2. 适合 “分类统计” 场景(如 “每个专业有多少学生”)。
合并元素为字符串String collect4 = students.stream().map(Student::getName).collect(Collectors.joining(","));1. 先提取姓名,再用 joining(",") 按 “逗号” 拼接;2. 无需手动处理循环,直接得到合并后的字符串。
合并元素(通用场景)String reduce1 = students.stream().map(Student::getMajor).reduce("", (a, b) -> a + b);1. reduce 也可用于字符串拼接(初始值为空串,累加器是字符串拼接);2. 相比 joiningreduce 更灵活,但 joining 对字符串场景更简洁。

总结:Stream 操作的核心逻辑(3 步)

  1. 明确需求:先想清楚要做什么(过滤?转换?统计?);
  2. 选中间操作:用 filter/map/sorted 等定义 “处理规则”(生成新流,不执行);
  3. 选终端操作:用 collect/sum/anyMatch 等触发 “执行并得到结果”(终端操作只能有一个)。

Stream 的核心特性(避免误用)、常见坑点(避坑指南)、Optional 配合使用(安全处理空值)

一、Stream 核心特性:必须记住的 “规则”

这些特性决定了 Stream 的使用边界,不注意就会出问题:

1. 不可重复消费(Stream 是 “一次性” 的)
  • 问题:一个 Stream 对象只能执行一次终端操作(如 collectsumanyMatch),执行后流会被 “消耗”,再次调用终端操作会报错。
  • 示例
    Stream<Student> stream = students.stream().filter(s -> s.age > 20);
    stream.collect(Collectors.toList()); // 第一次终端操作:正常
    stream.count(); // 第二次终端操作:报错(IllegalStateException: stream has already been operated upon or closed)
    
  • 解决:每次需要处理时,重新生成流(如 students.stream() 每次调用都会创建新流)。
2. 并行流(Parallel Stream):高效处理大数据,但需注意线程安全
  • 适用场景:数据量极大(如上万条数据),想利用多核 CPU 加速处理时,可将普通流转为并行流。
  • 用法:在 stream() 后加 parallel(),或直接用 parallelStream()
    // 并行流计算年龄总和(数据量大时比普通流快)
    int totalAge = students.parallelStream().mapToInt(Student::getAge).sum();
    
  • 坑点:并行流会多线程处理元素,若操作中涉及 线程不安全的集合(如 ArrayList),会导致数据错误:
    // 错误示例:ArrayList 线程不安全,并行流添加元素会丢数据
    List<String> names = new ArrayList<>();
    students.parallelStream().map(Student::getName).forEach(names::add); // 可能出现元素丢失或数组越界// 正确示例:用线程安全的集合,或直接 collect
    List<String> names = students.parallelStream().map(Student::getName).collect(Collectors.toList()); // collect 内部会处理线程安全
    

二、常见坑点:避开这些 “隐形错误”

1. 空指针问题(流中存在 null 元素)
  • 问题:若流中包含 null 对象,后续 mapfilter 操作调用对象方法时,会直接抛出 NullPointerException
  • 示例
    // 假设 students 中混入一个 null(如从数据库查询时可能出现)
    List<Student> students = Arrays.asList(new Student("Alice",22,"CS"), null, new Student("Bob",21,"Math"));// 错误:调用 null.getName() 会抛空指针
    students.stream().map(Student::getName).collect(Collectors.toList());// 解决:先过滤掉 null 元素
    students.stream().filter(Objects::nonNull) // 过滤 null,Objects::nonNull 是方法引用,等价于 s -> s != null.map(Student::getName).collect(Collectors.toList());
    
2. sorted 排序的 “隐性要求”
  • 问题:若对自定义对象(如 Student)用 sorted() 且未指定比较器,会抛出 ClassCastException—— 因为 Student 未实现 Comparable 接口。
  • 示例
    // 错误:Student 未实现 Comparable,直接 sorted() 会报错
    students.stream().sorted().collect(Collectors.toList());// 正确:必须指定比较器(如按年龄排序)
    students.stream().sorted(Comparator.comparingInt(Student::getAge)).collect(Collectors.toList());
    
3. Collectors.toMap 的 “键重复” 问题
  • 问题:用 Collectors.toMap 时,若流中存在 重复的 key(如两个学生专业相同),且未指定 “重复键处理规则”,会抛出 IllegalStateException
  • 示例
    // 错误:若有两个学生专业都是 "Mathematics",会抛键重复异常
    Map<String, Student> map = students.stream().collect(Collectors.toMap(Student::getMajor, s -> s)); // 无重复处理规则// 正确:指定重复键时保留旧值或新值
    Map<String, Student> map = students.stream().collect(Collectors.toMap(Student::getMajor, s -> s, (oldVal, newVal) -> oldVal // 重复时保留旧值;若想保留新值,改写成 (o,n) -> n));
    

三、Optional 配合使用:安全处理 “无结果” 场景

之前的 average()reduce() 会返回 Optional 类型(如 OptionalDoubleOptional<Integer>),其核心作用是 避免空指针,安全处理 “流为空时无结果” 的情况。

错误用法:直接 get(),可能抛异常
// 错误:若 students 为空,average() 返回 OptionalDouble.empty,getAsDouble() 会抛 NoSuchElementException
double avg = students.stream().mapToInt(Student::getAge).average().getAsDouble();
正确用法:用 orElseifPresent 等方法处理空值
方法作用示例代码
orElse(默认值)无结果时返回默认值double avg = students.stream().mapToInt(Student::getAge).average().orElse(0.0);
orElseGet(供应器)无结果时通过函数生成默认值(更灵活)double avg = students.stream().mapToInt(Student::getAge).average().orElseGet(() -> 0.0);
ifPresent(消费者)有结果时才执行操作(避免判断 null)students.stream().mapToInt(Student::getAge).average().ifPresent(avg -> System.out.println("平均年龄:" + avg));

四、补充小技巧:提升开发效率

1. 链式操作的 “最优顺序”:先过滤,再转换 / 排序
  • 原理filter 会减少流中元素的数量,后续的 mapsorted 等操作处理的数据量减少,效率更高。
  • 示例
    // 推荐:先过滤(年龄>20),再提取姓名(减少 map 处理的元素)
    List<String> names = students.stream().filter(s -> s.age > 20).map(Student::getName).collect(Collectors.toList());// 不推荐:先 map 再 filter,多处理了年龄≤20的元素
    List<String> names = students.stream().map(Student::getName).filter(name -> students.stream().filter(s -> s.getName().equals(name)).findFirst().get().age > 20).collect(Collectors.toList());
    
2. 用 peek 调试 Stream 流程
  • 作用peek 是中间操作,可在流的每个元素处理时 “插入调试逻辑”(如打印元素),不改变元素本身,方便定位问题。
  • 示例
    // 查看过滤、转换的中间结果
    students.stream().peek(s -> System.out.println("原始元素:" + s.getName())) // 打印原始元素.filter(s -> s.age > 20).peek(s -> System.out.println("过滤后元素:" + s.getName())) // 打印过滤后元素.map(Student::getName).peek(name -> System.out.println("转换后元素:" + name)) // 打印转换后元素.collect(Collectors.toList());
http://www.dtcms.com/a/499438.html

相关文章:

  • 虚幻引擎5 GAS开发俯视角RPG游戏 P06-06 次要属性
  • 基于Vue的保护动物信息管理系统r7zl6b88 (程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 深圳AI搜索优化怎么样
  • 工商网站查询企业信息官网购物平台最新排名
  • 【JUC】核心知识点归纳
  • 网站上海备案大渡口发布
  • 虚幻引擎入门教程:虚幻编辑器的基本操作
  • 建行网站首页登录网上银行百度服务中心官网
  • chrome 浏览器更新
  • 单元测试、系统测试、集成测试知识总结
  • 【C++/Lua联合开发】 (三) C++调用Lua
  • 网站开发与黑客做网站维护需要懂什么
  • C++动态规划——LIS(最长不下降子序列)
  • 计算 CIDR 块包含 C 类地址数量的方法
  • [创业之路-702]:“第三次”与“第四次工业革命”的范式跃迁
  • php 简单购物网站diy定制网站
  • 《vector.pdf 深度解读:vector 核心接口、扩容机制与迭代器失效解决方案》
  • Linux中slab缓存初始化kmem_cache_init函数和定时回收函数的实现
  • 南头专业的网站建设公司厦门网站建设公司怎么选
  • 郑州市做网站的公司西安有什么好玩的地方吗
  • Java 大视界 -- 金融市场情绪预测与动态决策的 Java 大数据实战(2024 券商落地版 425)
  • 运维干货:Nginx 常用配置与问题排查指南
  • 条款16:保证const成员函数的线程安全性
  • 网站开发需求现在网站怎么备案
  • 巧用LEF实现row aware track规划
  • 大话数据结构之 <栈> 和<队列>(C语言)
  • Windows 系统的 Delivery Optimization后台用了几GB流量,如何暂停?
  • 基于ads1256的ADC控制实现
  • 建站之星破解版手机正规建网站企业
  • 建一个电商网站要多少钱wordpress及时聊天