禅与计算机 禅与计算机
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

sharkchili

计算机禅修者
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析 Java 泛型的魅力与机制
    • 来聊聊Java为什么只有值传递
    • 来聊聊大厂常问的SPI工作原理
    • 来聊聊session与token的区别
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • 一文带你速通HashMap底层核心数据结构红黑树
    • 深入HashMap底层理解阿里手册的遍历守则
    • LinkedHashMap源码到面试题的全解析
    • 空间预分配思想提升HashMap插入效率
    • 解析Java集合工具类:功能与实践
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • 聊聊Java关于IO流中的设计模式
    • 为什么流不关闭会导致内存泄漏
    • 聊聊java零拷贝的几种实现
    • Java8流式编程入门
    • Java8流式编程详解
      • 简介
      • 前置铺垫
      • 元素的筛选
        • 常规过滤筛选
        • 找出不重复元素
        • 限制筛选元素
        • 跳过某些元素进行筛选
      • 映射
        • 映射转换
        • 映射扁平化
      • 查找和匹配
        • 检查是否至少匹配一个元素
        • 检查流元素是否都匹配
        • 检查流元素是否都不都匹配
        • 查找元素
        • 查找第一个元素
      • 规约
        • 元素求和
        • 求最大值和最小值
        • 实践题
      • 实践
        • 需求描述
        • 问题
        • 对应答案
      • 参考文献
    • 来聊聊java8的数值流
    • 聊聊Java8中的函数式编程
    • 一文速通lambda与函数式编程
    • 基于lambda简化设计模式
    • Java8函数式方法引用最佳实践
    • 使用Java8并行流的注意事项
    • 详解java数值类型核心知识点
    • 将一维数组按指定长度转为二维数组
    • 33个非常实用的JavaScript一行代码
    • 多种数组去重性能对比
    • 防抖与节流函数
    • 比typeof运算符更准确的类型判断
    • new命令原理
    • ES6面向对象
    • ES5面向对象
    • 判断是否为移动端浏览器
    • JS随机打乱数组
    • JS获取和修改url参数
    • 三级目录

  • 并发编程

  • JVM相关

  • 深入理解Spring框架

  • Java核心技术
  • Java基础
sharkchili
2023-10-17
目录

Java8流式编程详解

# 简介

java8提供的流式编程使得我们对于集合的处理不再是临时集合加上各种还能for循环,取而代之的是更加简洁高效的流水线操作,所以笔者就以这篇文章总结一下流式编程中常见的操作。

# 前置铺垫

后文示例操作中,我们都会基于这个菜肴类的集合展开,对于菜肴类的代码如下:

public class Dish {

    /**
     * 名称
     */
    private final String name;
    /**
     * 是否是素食
     */
    private final boolean vegetarian;
    /**
     * 卡路里
     */
    private final int calories;
    /**
     * 类型
     */
    private final Type type;

    //类型枚举 分别是是:肉类 鱼类 其他
    public enum Type {MEAT, FISH, OTHER}

    public Dish(String name, boolean vegetarian, int calories, Type type) {
        this.name = name;
        this.vegetarian = vegetarian;
        this.calories = calories;
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public boolean isVegetarian() {
        return vegetarian;
    }

    public int getCalories() {
        return calories;
    }

    public Type getType() {
        return type;
    }


    @Override
    public String toString() {
        return name;
    }


}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

同理我们也给出集合声明和初始化代码段:

 public static final List<Dish> menu =
            Arrays.asList(new Dish("pork", false, 800, Dish.Type.MEAT),
                    new Dish("beef", false, 700, Dish.Type.MEAT),
                    new Dish("chicken", false, 400, Dish.Type.MEAT),
                    new Dish("french fries", true, 530, Dish.Type.OTHER),
                    new Dish("rice", true, 350, Dish.Type.OTHER),
                    new Dish("season fruit", true, 120, Dish.Type.OTHER),
                    new Dish("pizza", true, 550, Dish.Type.OTHER),
                    new Dish("prawns", false, 400, Dish.Type.FISH),
                    new Dish("salmon", false, 450, Dish.Type.FISH));
1
2
3
4
5
6
7
8
9
10

# 元素的筛选

# 常规过滤筛选

菜肴类的vegetarian这个布尔值决定了菜肴是否为蔬菜,我们希望从这个集合中找到所有的蔬菜并返回一个list。

此时,我们就可以使用filter,它要求传入一个表达式,最终返回一个布尔值,所以我们直接将Dish的isVegetarian传入,如果是蔬菜则返回true,进入下一个流水线操作,反之则直接淘汰这个元素:

public static void main(String[] args) {
        List<Dish> vegetarianMenu =
                menu.stream()
                        //使用filter 结合函数式编程筛选出vegetarian 为true的菜肴
                        .filter(Dish::isVegetarian)
                        //将这些流组成一个list数组
                        .collect(toList());
        //遍历vegetarianMenu
        vegetarianMenu.forEach(System.out::println);

    }
1
2
3
4
5
6
7
8
9
10
11

最终输出结果如下,符合预期:

french fries
rice
season fruit
pizza
1
2
3
4

这种写法写起来就像SQL语句一样,我们无需各种for循环的声明指令,而是像是一种声明式的操作,而流的工作原理也如下图所示,将每一个元素放到流式操作的流水线上,符合预期的存入list,不符合预期的淘汰:

在这里插入图片描述

# 找出不重复元素

我们现在有一个无序且包含重复元素的整型数组,代码如下所示,希望能从中筛选出能够被2整除的数字并构成一个list集合:

  List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
1

在java8之前,你的编码思路可能会是:

  1. for循环找到被2整除的元素。
  2. 将该元素存到set中。
  3. 将set转转为list。

java8之后我们无需这么繁琐,我们只需针对流进行如下操作:

  1. 拿到集合流。
  2. 使用filter过滤出被2整除的元素,传到流水线下一步工序。
  3. 使用distinct判断是否重复。
  4. 存入list中。

对应代码如下:

public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
        List<Integer> integerList = numbers.stream()
                //过滤出能够被2整除的数字
                .filter(i -> i % 2 == 0)
                //去重
                .distinct()
                //循环遍历
                .collect(Collectors.toList());
		//遍历输出结果
        for (Integer integer : integerList) {
            System.out.println(integer);
        }

    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

查看其输出结果,也是符合预期的:

2
4
1
2

再来看看流的工作图,他的操作也同样如流水线一般,我们这里就以数字1和数字2为例讲解一下流的工作过程:

  1. 流拿到数字1,走到filter表达式,返回false,直接淘汰。
  2. 流拿到数字2,走到filter表达式,返回true,走到下一个操作。
  3. 数字2执行distinct,不重复,进入下一个操作。
  4. 数字2存入list中。
  5. 流再次拿到数字2,走到filter表达式,返回true,走到下一个操作。
  6. 数字2执行distinct,重复,直接淘汰。

总的来说,流水编程之所以高效是因为它的流水线对于不符合要求的元素会直接过滤,相对于指令时的按部就班进行简洁许多:

在这里插入图片描述

# 限制筛选元素

以上文的菜肴集合为例,我们希望找到热量大于300卡的前3道菜,如果在java8之前,你一定的for循环加上一个count变量,当找到三道菜了就停止循环。 而java8提供了语义化的操作limit方法,我们只需通过filter找到大于300卡的菜肴后,直接使用limit就可以完成限制筛选了

public static void main(String[] args) {
        List<Dish> menuList = menu.stream()
                //过滤出300卡的菜肴
                        .filter(d -> d.getCalories() > 300)
                //筛选出最先3个符合预期的菜肴
                        .limit(3)
                        .collect(toList());

        for (Dish dish : menuList) {
            System.out.println(dish);
        }

    }
1
2
3
4
5
6
7
8
9
10
11
12
13

对应的输出结果如下,符合预期:

pork
beef
chicken
1
2
3

# 跳过某些元素进行筛选

我们希望跳过前两个高于300卡的菜,再筛选出3道高于300卡的菜肴又该怎么办呢?对于java8而言,只需加一个skip操作即可完成这个需求:

public static void main(String[] args) {
        List<Dish> menuList = menu.stream()
                //过滤出300卡的菜肴
                        .filter(d -> d.getCalories() > 300)
                //跳过前两道热量高于300卡的菜
                .skip(2)
                //筛选出最先3个符合预期的菜肴
                        .limit(3)
                        .collect(toList());

        for (Dish dish : menuList) {
            System.out.println(dish);
        }

    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

从输出结果来看,确实高于300卡的前两个被剔除,然后再筛选出前3个元素:

chicken
french fries
rice
1
2
3

对应的原理图如下所示,可以看到经过filter表达式后,前两个高于300卡的元素直接被剔除:

在这里插入图片描述

# 映射

# 映射转换

映射操作其实也很好理解,就像使用sql语句一样通过select出你所需要的字段,例如我们希望从菜肴中拿到所有菜肴的名字,如果在java8之前,你一定会声明一个List<String>的临时集合,然后遍历菜肴集合,获取到每个菜肴的名字,添加到List<String>这个集合中。

java8直接基于map方法即可映射出自己所需要的成员属性,结合终端操作collect(toList())即可直接将映射结果转为list,避免了用户创建临时集合等繁琐操作:

public static void main(String[] args) {
        List<String> dishNames = menu.stream()
                //拿到每一个菜肴的名字
                .map(Dish::getName)
                //存入list集合中
                .collect(toList());

        for (String dishName : dishNames) {
            System.out.println(dishName);
        }


    }
1
2
3
4
5
6
7
8
9
10
11
12
13

输出结果:

pork
beef
chicken
french fries
rice
season fruit
pizza
prawns
salmon
1
2
3
4
5
6
7
8
9

# 映射扁平化

我们有 List<String> 的单词数组,我们希望找出数组中所有不重复的字母并输出,对应的单词数组定义如下:

 List<String> words = Arrays.asList("Hello", "World");
1

这时候你第一时间可能想到这样,将数组中每个单词切成一个个字母,然后distinct去重,最后转成list输出

public static void main(String[] args) {
        List<String> words = Arrays.asList("Hello", "World");


        List<String[]> resultList = words.stream()
                //这里映射的是stream<String[]> 后续的中间操作没有什么作用
                .map(w -> w.split(""))
                .distinct()
                .collect(toList());

        for (String[] strings : resultList) {
            for (String string : strings) {
                System.out.print(string);
            }
            System.out.println();
        }



    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

可以看到,这样的做法,最终得到的是一个 List<String[]>,代码如下,其工作原理也很好解释,map(w -> w.split(""))会将hello World分别切割成String[],然后两个String[],很显然对一个数组distinct自然是没有任何作用的:

在这里插入图片描述

打印以下输出结果

Hello
World
1
2

所以我们希望map操作后的数组转为String的stream流,于是我们就有了第2个版本的写法,我们通过map(Arrays::stream),意将切割后的string[]转为Stream<String>再进行去重:

public static void main(String[] args) {
        List<String> words = Arrays.asList("Hello", "World");

        List<Stream<String>> list = words.stream()
                .map(w -> w.split(""))
                //将stream<String[]>转为stream<String> 但还是没有解决问题 因为将数组变成string流
                .map(Arrays::stream)
                .distinct()
                .collect(toList());

        for (Stream<String> stream : list) {
            List<String> stringList = stream.collect(Collectors.toList());
            for (String s : stringList) {
                System.out.print(s);
            }

            System.out.println();
        }


    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

,但是去重再转为数组后得到类型却是 List<Stream<String>>,这意味着我们只是将两个单词转为两个独立的stream<String>:

在这里插入图片描述

所以我们需要更进一步的操作,来看看正确的写法,通过flatMap调用Arrays::stream,即可完成将两个独立的Stream<String>合并成一个扁平的Stream<String>最终得到正确结果,他的工作流程图也如下图所示

public static void main(String[] args) {
        List<String> words = Arrays.asList("Hello", "World");

        List<String> wordList = words.stream()
                .map(w -> w.split(""))
                //将数组扁平化合并为流
                .flatMap(Arrays::stream)
                .distinct()
                .collect(toList());

        for (String s : wordList) {
            System.out.println(s);
        }


    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

在这里插入图片描述

最终输出结果如下,符合预期:

H e l o W r d 

1
2

需要补充的是,对于扁平化的代码我们可以简化,我们可以直接通过表达式拿到集合中的元素在进行切割、扁平化操作:

  words.stream()
                 .flatMap((String line) -> Arrays.stream(line.split("")))
                 .distinct()
                 .forEach(System.out::println);
1
2
3
4

# 查找和匹配

# 检查是否至少匹配一个元素

我想知道菜肴集合中是否存在素菜,就可以使用anyMatch方法,该方法是一个终端操作,他将返回一个boolean值,只要集合中看到一个素食的菜肴,那么这样流操作就会返回true:

public static void main(String[] args) {
        System.out.println(menu.stream().anyMatch(Dish::isVegetarian));
    }
1
2
3

输出结果:

true
1

通过peek方法查看anyMatch工作流程:

public static void main(String[] args) {
        menu.stream()
                .peek(m -> System.out.println(m))
                .anyMatch(Dish::isVegetarian);

    }
1
2
3
4
5
6

从peek的输出结果来看,anyMatch只要匹配到符合要求的元素之后,就立即停止并关闭流了:

pork
beef
chicken
french fries
1
2
3
4

anyMatch的工作流程:

在这里插入图片描述

# 检查流元素是否都匹配

同理如果需要全部匹配,我们就用allMatch,例如我们需要判断菜肴中的菜肴是否都是低于1000卡的,我们完全可以这样写:

        menu.stream()
        	.allMatch(d -> d.getCalories() < 1000);
1
2

# 检查流元素是否都不都匹配

反过来来说,我们可以要找低于1000卡的,同样我们也可以使用下面这样的写法,判断是否不包含高于1000卡的菜肴

        menu.stream()
        	.noneMatch(d -> d.getCalories() >= 1000);
1
2

# 查找元素

我们希望找出菜肴中是否包含素食,如果有则告知查到的素食是什么菜,我们就可以通过使用findAny方法做到这一点:

public static void main(String[] args) {
        Optional<Dish> optionalDish = menu.stream()
                .filter(Dish::isVegetarian)
                //只要找到符合要求的菜肴,就直接停止并关闭流
                .findAny();

        //如果可以找到符合要求的元素,则直接输出打印
        if (optionalDish.isPresent()){
            System.out.println(optionalDish.get());
        }


    }
1
2
3
4
5
6
7
8
9
10
11
12
13

但是细心的读者看到了返回值竟然不是Dish而是Optional<Dish>,所以我们需要补充以下Optional的相关概念:

  1. Optional有个isPresent()方法,就以本例来说,假如集合中包含素食,我们调用这个方法就会返回true,说明找到了素食,反之返回false
  2. get()方法,以本示例来说,假如找到了素食,get就会返回菜肴对象,若没找到则报出NoSuchElement异常
  3. orElse()相比上一个方法更加友好,假如我们找到值就返回值,反之就返回orElse传入的参数值。

输出结果:

french fries
1

# 查找第一个元素

相比于查找元素,查找第一个元素语义化更加明显,例如我们想找到第一道素食,我们就可以使用findFirst,实际上关于findAny和findFirst的使用场景区别不大,但是在并行的情况下,你想找到第一道素食的话,建议你使用findFirst,反之使用findAny即可,因为它使用并行流来说限制较少一些:

 public static void main(String[] args) {
        Optional<Dish> optionalDish = menu.stream()
                .filter(Dish::isVegetarian)
                //只要找到符合要求的菜肴,就直接停止并关闭流
                .findFirst();

        //如果可以找到符合要求的元素,则直接输出打印
        if (optionalDish.isPresent()){
            System.out.println(optionalDish.get());
        }


    }
1
2
3
4
5
6
7
8
9
10
11
12
13

# 规约

# 元素求和

在java8之前,元素求和计算都需要经过这三步:

  1. 创建sum变量。
  2. 遍历集合,取出集合元素。
  3. 元素就和。

java8之后我们只需使用reduce结合求和表达式即可实现元素求和:

public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(4, 5, 3, 9);
        //起始数字为0,将列表中的元素全部累加
        int res = numbers.stream()
                .reduce(0, (sum, num) ->sum+num);
        //输出结果
        System.out.println(res);

    }
1
2
3
4
5
6
7
8
9

输出结果如下:

21
1

其工作原理如下图所示,即以我们传入的identity作为起始值,遍历列表中的每个数字与其进行累加:

在这里插入图片描述

基于上述代码我们使用Integer内置的一个相加的方法sum,于是代码就可以简化成下面这样:

public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(4, 5, 3, 9);
        //起始数字为0,将列表中的元素全部累加
        int res = numbers.stream()
                .reduce(0, Integer::sum);
        //输出结果
        System.out.println(res);

    }
1
2
3
4
5
6
7
8
9

假如你的统计操作无需初始值的话,也可以像下文这种写法,这正是java8的高明之处,返回一个Optional操作,让你有余地进行判空操作

·//无需初始值
        Optional<Integer> res = numbers.stream().reduce(Integer::sum);
        System.out.println(res.get());
1
2
3

# 求最大值和最小值

最大值和最小值做法和上文差不多,同样是基于reduce结合最大值或者最小值方法即可:

		//求最大值
		int max = numbers.stream().reduce(0, (a, b) -> Integer.max(a, b));
        System.out.println(max);
		//求最最小值
        Optional<Integer> min = numbers.stream().reduce(Integer::min);
        min.ifPresent(System.out::println);
1
2
3
4
5
6

# 实践题

就以博客最早提到的菜肴类,我们希望用流式编程统计出菜肴的数量,可以看到笔者的做法很巧妙,通过映射将每道菜计算为1,传到reduce流中统计计算

 Optional<Integer> menuCount = menu.stream()
                .map(d -> 1)
                .reduce(Integer::sum);
        System.out.println(menuCount.get());
1
2
3
4

我们甚至可以简写成这样,因为java8为我们提供了这样的终端操作

 long count = menu.stream().count();
        System.out.println(count);
1
2

# 实践

# 需求描述

我们首先定义一个交易员的类,该类描述了交易员的姓名和居住城市:

public class Trader {

    /**
     * 姓名
     */
    private String name;
    /**
     * 居住城市
     */
    private String city;

    public Trader(String n, String c) {
        this.name = n;
        this.city = c;
    }

    public String getName() {
        return this.name;
    }

    public String getCity() {
        return this.city;
    }

    public void setCity(String newCity) {
        this.city = newCity;
    }

    public String toString() {
        return "Trader:" + this.name + " in " + this.city;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

还有一个交易订单类,它描述了订单的交易年份、交以金额、和交易这笔订单的人员:

public class Transaction{

    /**
     * 交易员
     */
    private Trader trader;
    /**
     * 交易年份
     */
    private int year;
    /**
     * 交以金额
     */
    private int value;

    public Transaction(Trader trader, int year, int value)
    {
        this.trader = trader;
        this.year = year;
        this.value = value;
    }

    public Trader getTrader(){
        return this.trader;
    }

    public int getYear(){
        return this.year;
    }

    public int getValue(){
        return this.value;
    }

    public String toString(){
        return "{" + this.trader + ", " +
                "year: "+this.year+", " +
                "value:" + this.value +"}";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

用这两个类,构成了一个关于订单的列表:

		//4个交易员
 		Trader raoul = new Trader("Raoul", "Cambridge");
        Trader mario = new Trader("Mario", "Milan");
        Trader alan = new Trader("Alan", "Cambridge");
        Trader brian = new Trader("Brian", "Cambridge");

		//4个交易员的订单总表
        List<Transaction> transactions = Arrays.asList(
                new Transaction(brian, 2011, 300),
                new Transaction(raoul, 2012, 1000),
                new Transaction(raoul, 2011, 400),
                new Transaction(mario, 2012, 710),
                new Transaction(mario, 2012, 700),
                new Transaction(alan, 2012, 950)
        );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 问题

基于上述代码,我们给出下面几道问题:

(1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。
(2) 交易员都在哪些不同的城市工作过?
(3) 查找所有来自于剑桥的交易员,并按姓名排序。
(4) 返回所有交易员的姓名字符串,按字母顺序排序。
(5) 有没有交易员是在米兰工作的?
(6) 打印生活在剑桥的交易员的所有交易额。
(7) 所有交易中,最高的交易额是多少?
(8) 找到交易额最小的交易。

# 对应答案

上述8个问题笔者都已给出答案,读者在练习完成后可自行核对:

问题1:

        List<Transaction> tr2011 = transactions.stream()
                .filter(transaction -> transaction.getYear() == 2011)
                .sorted(comparing(Transaction::getValue))
                .collect(toList());
        System.out.println(tr2011);
        //找出2011年发生的所有交易,并按交易额排序(从低到高)
        List<Transaction> transactions_2011 = transactions.stream()
                .filter(t -> t.getYear() == 2011)
                .sorted(comparing(Transaction::getValue))
                .collect(toList());
        System.out.println(transactions_2011);
1
2
3
4
5
6
7
8
9
10
11

问题2:

        List<String> cities =
                transactions.stream()
                        .map(transaction -> transaction.getTrader().getCity())
                        .distinct()
                        .collect(toList());
        System.out.println(cities);

        //交易员都在哪些不同的城市工作过
        List<String> citys = transactions.stream()
                .map(Transaction::getTrader)
                .map(Trader::getCity)
                .distinct()
                .collect(toList());
        System.out.println(citys);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

问题3:


        List<Trader> traders =
                transactions.stream()
                        .map(Transaction::getTrader)
                        .filter(trader -> trader.getCity().equals("Cambridge"))
                        .distinct()
                        .sorted(comparing(Trader::getName))
                        .collect(toList());
        System.out.println(traders);

        //查找所有来自于剑桥的交易员,并按姓名排序
        List<Trader> cambridgeTraders = transactions.stream()
                .map(Transaction::getTrader)
                .filter(t -> "Cambridge".equals(t.getCity()))
                .distinct()
                .sorted(comparing(Trader::getName))
                .collect(toList());
        System.out.println(cambridgeTraders);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

问题4:


        String traderStr =
                transactions.stream()
                        .map(transaction -> transaction.getTrader().getName())
                        .distinct()
                        .sorted()
                        .reduce("", (n1, n2) -> n1 + n2);
        System.out.println(traderStr);

        //返回所有交易员的姓名字符串,按字母顺序排序。×
        String names = transactions.stream()
                .map(Transaction::getTrader)
                .map(Trader::getName)
                .distinct()
                .sorted()
                .reduce("", (s1, s2) -> s1 + s2);

        System.out.println(names);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

问题5:


        boolean milanBased =
                transactions.stream()
                        .anyMatch(transaction -> transaction.getTrader()
                                .getCity()
                                .equals("Milan")
                        );
        System.out.println(milanBased);

        //(5) 有没有交易员是在米兰工作的? 
        boolean hasMilan = transactions.stream()
                .anyMatch(t -> "Milan".equals(t.getTrader().getCity()));
1
2
3
4
5
6
7
8
9
10
11
12

问题6:

        transactions.stream()
                .map(Transaction::getTrader)
                .filter(trader -> trader.getCity().equals("Milan"))
                .forEach(trader -> trader.setCity("Cambridge"));
        System.out.println(transactions);
        //(6) 打印生活在剑桥的交易员的所有交易额。
        Optional<Integer> sum = transactions.stream()
                .filter(t -> "Cambridge".equals(t.getTrader().getCity()))
                .map(Transaction::getValue)
                .reduce(Integer::sum);
        System.out.println(sum.get());
1
2
3
4
5
6
7
8
9
10
11

问题7:

        int highestValue =
                transactions.stream()
                        .map(Transaction::getValue)
                        .reduce(0, Integer::max);
        System.out.println(highestValue);


        //(7) 所有交易中,最高的交易额是多少?
        Optional<Integer> max = transactions.stream()
                .map(Transaction::getValue)
                .reduce(Integer::max);
        System.out.println(max.get());
1
2
3
4
5
6
7
8
9
10
11
12

问题8:


        Optional<Transaction> min2 = transactions.stream()
                .reduce((t1, t2) -> t1.getValue() < t2.getValue() ? t1 : t2);
        Optional<Transaction> min3 = transactions.stream()
                .min(comparing(Transaction::getValue));


        System.out.println(min +" "+ min2 +" " +min3);
1
2
3
4
5
6
7
8

# 参考文献

Java 8实战:https://book.douban.com/subject/25912747/ (opens new window)

编辑 (opens new window)
上次更新: 2026/03/26, 01:05:31
Java8流式编程入门
来聊聊java8的数值流

← Java8流式编程入门 来聊聊java8的数值流→

最近更新
01
基于EasyExcel实现高效导出
03-25
02
从开源框架中学习那些实用的位运算技巧
03-25
03
浅谈分布式架构设计思想和常见优化手段
03-25
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×