实战SpringCloud微服务从青铜到王者
最后更新于:2022-04-02 07:57:59
访问地址:[实战SpringCloud微服务从青铜到王者](https://www.kancloud.cn/hanxt/springcloud/content)
# 章节目录
* [文档内容简介(一定要看)](https://www.kancloud.cn/hanxt/springcloud/content)
* [模块与代码分支说明](https://www.kancloud.cn/hanxt/springcloud/content)
* [dongbb-cloud项目核心架构](https://www.kancloud.cn/hanxt/springcloud/content)
* [微服务架构进化论](https://www.kancloud.cn/hanxt/springcloud/content)
* [SpringBoot与Cloud选型兼容](https://www.kancloud.cn/hanxt/springcloud/content)
* [Spring Cloud组件的选型](https://www.kancloud.cn/hanxt/springcloud/content)
* [单体应用拆分微服务](https://www.kancloud.cn/hanxt/springcloud/content)
* [单体应用与微服务对比](https://www.kancloud.cn/hanxt/springcloud/content)
* [微服务设计拆分原则](https://www.kancloud.cn/hanxt/springcloud/content)
* [新建父工程及子模块框架](https://www.kancloud.cn/hanxt/springcloud/content)
* [通用微服务初始化模块构建](https://www.kancloud.cn/hanxt/springcloud/content)
* [持久层模块单独拆分](https://www.kancloud.cn/hanxt/springcloud/content)
* [拆分rbac权限管理微服务](https://www.kancloud.cn/hanxt/springcloud/content)
* [Hello-microservice](https://www.kancloud.cn/hanxt/springcloud/content)
* [构建eureka服务注册中心](https://www.kancloud.cn/hanxt/springcloud/content)
* [向服务注册中心注册服务](https://www.kancloud.cn/hanxt/springcloud/content)
* [第一个微服务调用](https://www.kancloud.cn/hanxt/springcloud/content)
* [远程服务调用](https://www.kancloud.cn/hanxt/springcloud/content)
* [HttpClient远程服务调用](https://www.kancloud.cn/hanxt/springcloud/content)
* [RestTemplate远程服务调用](https://www.kancloud.cn/hanxt/springcloud/content)
* [RestTemplate多实例负载均衡](https://www.kancloud.cn/hanxt/springcloud/content)
* [Ribbon调用流程源码解析](https://www.kancloud.cn/hanxt/springcloud/content)
* [Ribbon负载均衡策略源码解析](https://www.kancloud.cn/hanxt/springcloud/content)
* [Ribbon重试机制与饥饿加载](https://www.kancloud.cn/hanxt/springcloud/content)
* [Ribbon自定义负载均衡策略](https://www.kancloud.cn/hanxt/springcloud/content)
* [Feign与OpenFeign](https://www.kancloud.cn/hanxt/springcloud/content)
* [Feign设计原理源码解析](https://www.kancloud.cn/hanxt/springcloud/content)
* [Feign请求压缩与超时等配置](https://www.kancloud.cn/hanxt/springcloud/content)
* [服务注册与发现](https://www.kancloud.cn/hanxt/springcloud/content)
* [白话服务注册与发现](https://www.kancloud.cn/hanxt/springcloud/content)
* [DiscoveryClient服务发现](https://www.kancloud.cn/hanxt/springcloud/content)
* [Eureka集群环境构建(linux)](https://www.kancloud.cn/hanxt/springcloud/content)
* [Eureka集群多网卡环境ip设置](https://www.kancloud.cn/hanxt/springcloud/content)
* [Eureka集群服务注册与安全认证](https://www.kancloud.cn/hanxt/springcloud/content)
* [Eureka自我保护与健康检查](https://www.kancloud.cn/hanxt/springcloud/content)
* [主流服务注册中心对比(含nacos)](https://www.kancloud.cn/hanxt/springcloud/content)
* [zookeeper概念及功能简介](https://www.kancloud.cn/hanxt/springcloud/content)
* [zookeeper-linux集群安装](https://www.kancloud.cn/hanxt/springcloud/content)
* [zookeeper服务注册与发现](https://www.kancloud.cn/hanxt/springcloud/content)
* [consul概念及功能介绍](https://www.kancloud.cn/hanxt/springcloud/content)
* [consul-linux集群安装](https://www.kancloud.cn/hanxt/springcloud/content)
* [consul服务注册与发现](https://www.kancloud.cn/hanxt/springcloud/content)
* [通用-auatator导致401问题](https://www.kancloud.cn/hanxt/springcloud/content)
* [分布式配置中心-apollo](https://www.kancloud.cn/hanxt/springcloud/content)
* [服务配置中心概念及使用场景](https://www.kancloud.cn/hanxt/springcloud/content)
* [apollo概念功能简介](https://www.kancloud.cn/hanxt/springcloud/content)
* [apollo架构详解](https://www.kancloud.cn/hanxt/springcloud/content)
* [apollo分布式部署之Portal](https://www.kancloud.cn/hanxt/springcloud/content)
* [apollo分布式部署之环境区分](https://www.kancloud.cn/hanxt/springcloud/content)
* [apollo项目权限管理实战](https://www.kancloud.cn/hanxt/springcloud/content)
* [apollo-java客户端基础](https://www.kancloud.cn/hanxt/springcloud/content)
* [apollo与SpringCloud服务集成](https://www.kancloud.cn/hanxt/springcloud/content)
* [apollo实例配置热更新](https://www.kancloud.cn/hanxt/springcloud/content)
* [apollo命名空间与集群](https://www.kancloud.cn/hanxt/springcloud/content)
* [apollo灰度发布(日志热更新为例)](https://www.kancloud.cn/hanxt/springcloud/content)
* [SpringCloudConfig配置中心](https://www.kancloud.cn/hanxt/springcloud/content)
* [config-git配置文件仓库](https://www.kancloud.cn/hanxt/springcloud/content)
* [config配置中心搭建与测试](https://www.kancloud.cn/hanxt/springcloud/content)
* [config客户端基础](https://www.kancloud.cn/hanxt/springcloud/content)
* [config配置安全认证](https://www.kancloud.cn/hanxt/springcloud/content)
* [config客户端配置刷新](https://www.kancloud.cn/hanxt/springcloud/content)
* [config配置中心高可用](https://www.kancloud.cn/hanxt/springcloud/content)
* [BUS消息总线](https://www.kancloud.cn/hanxt/springcloud/content)
* [bus消息总线简介](https://www.kancloud.cn/hanxt/springcloud/content)
* [docker安装rabbitMQ](https://www.kancloud.cn/hanxt/springcloud/content)
* [基于rabbitMQ的消息总线](https://www.kancloud.cn/hanxt/springcloud/content)
* [bus实现批量配置刷新](https://www.kancloud.cn/hanxt/springcloud/content)
* [alibaba-nacos](https://www.kancloud.cn/hanxt/springcloud/content)
* [nacos介绍与单机部署](https://www.kancloud.cn/hanxt/springcloud/content)
* [nacos集群部署方式(linux)](https://www.kancloud.cn/hanxt/springcloud/content)
* [nacos服务注册与发现](https://www.kancloud.cn/hanxt/springcloud/content)
* [nacos服务注册中心详解](https://www.kancloud.cn/hanxt/springcloud/content)
* [nacos客户端配置加载](https://www.kancloud.cn/hanxt/springcloud/content)
* [nacos客户端配置刷新](https://www.kancloud.cn/hanxt/springcloud/content)
* [nacos服务配置隔离与共享](https://www.kancloud.cn/hanxt/springcloud/content)
* [nacos配置Beta发布](https://www.kancloud.cn/hanxt/springcloud/content)
* [服务熔断降级hystrix](https://www.kancloud.cn/hanxt/springcloud/content)
* [服务降级&熔断&限流](https://www.kancloud.cn/hanxt/springcloud/content)
* [Hystrix集成并实现服务熔断](https://www.kancloud.cn/hanxt/springcloud/content)
* [Jemter模拟触发服务熔断](https://www.kancloud.cn/hanxt/springcloud/content)
* [Hystrix服务降级fallback](https://www.kancloud.cn/hanxt/springcloud/content)
* [Hystrix结合Feign服务降级](https://www.kancloud.cn/hanxt/springcloud/content)
* [远程服务调用异常传递的问题](https://www.kancloud.cn/hanxt/springcloud/content)
* [Hystrix-Feign异常拦截与处理](https://www.kancloud.cn/hanxt/springcloud/content)
* [Hystrix-DashBoard单服务监控](https://www.kancloud.cn/hanxt/springcloud/content)
* [Hystrix-dashboard集群监控](https://www.kancloud.cn/hanxt/springcloud/content)
* [分布式系统流量卫兵sentinel](https://www.kancloud.cn/hanxt/springcloud/content)
* [sentinel简介与安装](https://www.kancloud.cn/hanxt/springcloud/content)
* [客户端集成与实时监控](https://www.kancloud.cn/hanxt/springcloud/content)
* [实战流控规则-QPS限流](https://www.kancloud.cn/hanxt/springcloud/content)
* [实战流控规则-线程数限流](https://www.kancloud.cn/hanxt/springcloud/content)
* [实战流控规则-关联限流](https://www.kancloud.cn/hanxt/springcloud/content)
* [实战流控规则-链路限流](https://www.kancloud.cn/hanxt/springcloud/content)
* [实战流控效果-WarmUp](https://www.kancloud.cn/hanxt/springcloud/content)
* [实战流控效果-匀速排队](https://www.kancloud.cn/hanxt/springcloud/content)
* [BlockException处理](https://www.kancloud.cn/hanxt/springcloud/content)
* [实战熔断降级-RT](https://www.kancloud.cn/hanxt/springcloud/content)
* [实战熔断降级-异常数与比例](https://www.kancloud.cn/hanxt/springcloud/content)
* [DegradeException处理](https://www.kancloud.cn/hanxt/springcloud/content)
* [注解与异常的归纳总结](https://www.kancloud.cn/hanxt/springcloud/content)
* [Feign降级及异常传递拦截](https://www.kancloud.cn/hanxt/springcloud/content)
* [动态规则nacos集中存储](https://www.kancloud.cn/hanxt/springcloud/content)
* [热点参数限流](https://www.kancloud.cn/hanxt/springcloud/content)
* [系统自适应限流](https://www.kancloud.cn/hanxt/springcloud/content)
* [微服务网关-GateWay](https://www.kancloud.cn/hanxt/springcloud/content)
* [还有必要学习Zuul么?](https://www.kancloud.cn/hanxt/springcloud/content)
* [简介与非阻塞异步IO模型](https://www.kancloud.cn/hanxt/springcloud/content)
* [GateWay概念与流程](https://www.kancloud.cn/hanxt/springcloud/content)
* [新建一个GateWay项目](https://www.kancloud.cn/hanxt/springcloud/content)
* [通用Predicate的使用](https://www.kancloud.cn/hanxt/springcloud/content)
* [自定义PredicateFactory](https://www.kancloud.cn/hanxt/springcloud/content)
* [编码方式构建静态路由](https://www.kancloud.cn/hanxt/springcloud/content)
* [Filter过滤器介绍与使用](https://www.kancloud.cn/hanxt/springcloud/content)
* [自定义过滤器Filter](https://www.kancloud.cn/hanxt/springcloud/content)
* [网关请求转发负载均衡](https://www.kancloud.cn/hanxt/springcloud/content)
* [结合nacos实现动态路由配置](https://www.kancloud.cn/hanxt/springcloud/content)
* [整合Sentinel实现资源限流](https://www.kancloud.cn/hanxt/springcloud/content)
* [跨域访问配置](https://www.kancloud.cn/hanxt/springcloud/content)
* [微服务网关安全认证-JWT篇](https://www.kancloud.cn/hanxt/springcloud/content)
* [Gateway-JWT认证鉴权流程](https://www.kancloud.cn/hanxt/springcloud/content)
* [登录认证JWT令牌颁发](https://www.kancloud.cn/hanxt/springcloud/content)
* [全局过滤器实现JWT鉴权](https://www.kancloud.cn/hanxt/springcloud/content)
* [微服务自身内部的权限管理](https://www.kancloud.cn/hanxt/springcloud/content)
* [微服务安全认证-OAuth篇(撰写中)](https://www.kancloud.cn/hanxt/springcloud/content)
';
实战前后端分离RBAC权限管理系统
最后更新于:2022-04-02 07:57:56
访问地址:[【实战开发】DongBB-前后端分离RBAC权限管理系统](https://www.kancloud.cn/hanxt/vue-spring/content)
# 章节目录
* 开发过程文档内容介绍
* 项目文档
* DongBB项目介绍
* 环境部署
* 阿里云CentOS7-搭建Mysql
* 阿里云CentOS7-JDK安装
* 阿里云CentOS7-安装nginx
* 部署SpringBoot后端应用
* 前端vue打包部署nginx
* 基础框架搭建
* 【前端】搭建vue前端框架
* 【前端】目录结构及配置调整
* 【前端】封装及使用axios
* 【后端】IDEA多模块springboot项目
* 【后端】统一接口响应的数据结构
* 实现JWT认证授权
* 【后端】数据库权限模型设计
* 【后端】jwt-spring-boot-starter说明
* 【后端】JWT认证及令牌刷新接口
* 【前端】开发用户密码登录页面
* 【前端】开发登录认证功能
* 【前端】JWT令牌的存储携带与刷新
* 【问题】跨域访问等问题的处理
* 系统布局菜单Tab前端实现
* 【前端】整体布局的实现
* 【前端】侧边栏多级菜单
* 【前端】菜单侧边栏收缩
* 【前端】JSON数据加载菜单
* 【前端】菜单项前端路由及组件
* 【前端】结合vuex实现导航tab
* 以用户信息为例讲接口鉴权设计
* 【前端】开发个人中心相关功能
* 【前端】导航Tab的功能优化
* 【后端】系统接口多层鉴权设计
* 【后端】持久层通用模块
* 【后端】以用户信息为例讲接口开发
* 【前端】用户信息接口与加载进度条
* 组织管理功能-树形结构精讲
* 【前端】理解树形表格展示结构
* 【后端】Mysql树形结构数据封装
* 【功能】Table查询重置的实现
* 【前端】树形下拉单选组件封装
* 【后端】增删改服务及接口实现
* 【前端】增删改功能的前端实现
* 菜单管理与接口分类管理
* 菜单管理功能类比实现(树形)
* 接口分类管理类比实现(树形)
* 角色管理与权限分配
* 【后端】角色管理CURD及接口定义
* 【前端】角色管理CURD
* 【前端】树形多项勾选组件封装
* 【后端】权限初始化及持久化接口
* 【前端】实现角色权限的分配
* 用户管理功能实现
* 【后端】用户管理后端接口及服务
* 【前端】查询分页与日期组件
* 【前端】使用组织树查询用户
* 【前端】增删改Mixin代码复用
* 【功能】用户角色分配功能开发
* 【功能】密码重置功能开发
* 【功能】登录提示修改默认密码
* 再谈菜单查看及接口访问权限
* 【使用】配置菜单接口角色用户权限
* 【后端】按数据库权限查询菜单
* 【前端】菜单栏数据加载渲染
* 全局配置参数功能设计与使用
* 【后端】数据库全局参数配置接口
* 【前端】加载及使用全局配置参数
* 【功能】参数配置管理功能开发
* 数据字典设计及使用
* 【后端】数据库数据字典接口
* 【前端】数据字典数据的加载
* 【前端】DictSelect组件封装与使用
* 【功能】数据字典管理功能的开发
* 1.0里程碑(优化补充调整)
* Header左侧样式优化
* Header右侧样式优化
* 为二级菜单增加图标
* 【bug修正】导航路由逻辑判断
';
Spring Security-JWT-OAuth2一本通
最后更新于:2022-04-02 07:57:54
访问地址:[Spring Security-JWT-OAuth2一本通](https://www.kancloud.cn/hanxt/springsecurity/content)
# 章节目录
* 第一章 spring security基础篇
* 1.1.spring-security简介并与shiro对比
* 1.2.需求分析与基础环境准备
* 1.3.HttpBasic模式登录认证
* 1.4.formLogin模式登录认证
* 1.5.源码解析登录验证流程
* 1.6.自定义登录验证结果处理
* 1.7.session会话的管理
* 第二章 认证授权鉴权功能深入
* 2.1.1.RBAC权限管理模型
* 2.1.2.结合真实系统讲解RBAC实现
* 2.2.加载动态数据进行登录与授权
* 2.3.动态加载资源鉴权规则
* 2.4.权限表达式使用方法总结
* 2.5.RememberMe记住我功能
* 2.6.退出登录功能的实现
* 2.7.多种图片验证码实现方案
* 2.8.基于session的图片验证码实现
* 2.9.短信验证码登录功能
* 2.10.账户多次登录失败锁定
* 第三章 前后端分离的应用认证
* 3.1.详述JWT使用场景及结构安全
* 3.2.Spring Security-JWT实现原理
* 3.3.编码实现JWT认证鉴权
* 3.4.解决跨域访问的问题
* 3.5.CSRF跨站攻击防护
* 3.6.JWT集群应用方案
* 第四章 SpringSocial社交登录
* 4.1.OAuth2授权标准简介
* 4.2.SpringSocia源码分析
* 4.3.QQ互联注册及应用创建
* 4.4.实现QQ登录功能
* 4.5.QQ登录功能细节处理
* 4.6.QQ登录用户关系绑定
* 第五章 Spring-Security-OAuth2项目
* 5.1.Spring与OAuth2发展路线图
* 5.2.实现授权码模式认证服务器
* 5.3.实现其它三种模式认证服务器
* 5.4.AccessToken令牌的刷新
* 5.5.编码实现资源服务器
* 5.6.认证资源服务器分离
* 5.7.认证资源服务整合JWT
* 附录一:抽取公共资源为独立模块
';
手摸手教你学Spring Boot2.0
最后更新于:2022-04-02 07:57:52
访问地址:[手摸手教你学Spring Boot2.0](https://www.kancloud.cn/hanxt/springboot2/content)
>这个章节目录已经有点旧了,后来我又写了很多东西,章节目录还未及时更新,内容只比这多,不比这少
## 章节目录
* 第一章 spring boot 2.x基础及概念入门
* 1.1.spring boot 产生的背景及其优势
* 1.2.spring boot 2.x 新特性说明
* 1.3.helloworld及项目结构介绍
* 1.4.IDEA结合spring boot开发技巧
* 第二章 RESTFul接口实现与测试
* 2.1.RESTFul接口与http协议状态表述
* 2.2.常用注解开发一个RESTFul接口
* 2.3 JSON数据处理与PostMan测试
* 2.4.使用Mockito编码完成接口测试
* 2.5. 使用Swagger2构建API文档
* 第三章 spring boot 配置原理实战
* 3.1.结合配置加载讲解bean自动装配原理
* 3.2.详解YAML语法及占位符语法
* 3.3.获取自定义配置的两种实现方法
* 3.4.配置文件注入值数据校验
* 3.5.加载旧项目配置文件的两种方式
* 3.6.profile不同环境使用不同配置
* 3.7.配置及配置文件的加载优先级
* 3.8.配置文件敏感字段加密
* 第四章 常用web开发数据库框架
* 4.1.整合Spring JDBC操作数据
* 4.2 Spring JDBC多数据源的实现
* 4.3.Spring JDBC JTA实现分布式事务
* 4.4.ORM主流框架选型
* 4.5.bean转换Dozer的快速上手
* 4.6.整合Spring Data JPA操作数据
* 4.7.Spring data JPA的多数据源实现
* 4.8.JPA+atomikos实现分布式事务
* 4.9.整合Mybatis操作数据
* 4.10.Mybatis开发最佳实践总结
* 4.11.Spring mybatis的多数据源实现
* 4.12.mybatis+atomikos实现分布式事务
* 4.13.Spring事务与分布式事务
* 4.14.整合Spring data mongodb操作数据
* 4.15.一行代码实现RESTFul接口
* 第五章 静态资源与模板引擎的整合
* 5.1.webjars与静态资源
* 5.2.模板引擎选型与未来趋势
* 5.3.web应用开发之整合jsp
* 5.4.web应用开发之整合freemarker
* 5.5.web应用开发之整合thymeleaf
* 5.6.thymeleaf基础语法讲解
* 5.7.thymeleaf内置对象与工具类
* 5.8.公共片段(标签)与内联js
* 第六章 生命周期内的拦截过滤与监听
* 6.1.servlet与filter与listener的实现
* 6.2.spring拦截器及请求链路说明
* 6.3.自定义事件的发布与监听
* 6.4.应用启动的监听
* 第七章 嵌入式容器的配置与应用
* 7.1.嵌入式的容器配置与调整
* 7.2.切换到jetty&undertow容器
* 7.3.打war包部署到外置tomcat容器
* 第八章 统一全局异常处理
* 8.1.设计一个优秀的异常处理机制
* 8.2.自定义异常和相关数据结构
* 8.3.全局异常处理ExceptionHandler
* 8.4.服务端数据校验与全局异常处理
* 8.5.AOP实现完美异常处理方案
* 第九章 日志框架与全局日志管理
* 9.1.日志框架的简介与选型
* 9.2.logback日志框架整合使用
* 9.3.log4j2日志框架整合与使用
* 9.4.拦截器实现统一访问日志
* 第十章 异步任务与定时任务
* 10.1.实现Async异步任务
* 10.2.为异步任务规划线程池
* 10.3.通过@Scheduled实现定时任务
* 10.4.quartz简单定时任务(内存持久化)
* 10.5.quartz动态定时任务(数据库持久化)
* 第十一章 redis缓存与session共享
* 11.1.使用docker安装redis
* 11.2.redis数据结构与应用场景
* 11.3.使用redisTemplate操作数据
* 11.4.使用Redis Repository操作数据
* 11.5.spring cache基本用法
* 11.6.详述缓存声明式注解的使用
* 11.7.个性化自定义缓存到期时间
* 11.8.集群多节点应用session共享
* 第十二章 整合分布式文件系统fastdfs
* 12.1.fastdfs简介及架构说明
* 12.2.使用docker安装fastdfs
* 12.3.开发一个自定义fastdfs-starter
* 12.4.整合fastdfs操作文件数据
* 第十三章 服务器推送技术
* 13.1.主流服务器推送技术说明
* 13.2.服务端推送事件SSE
* 13.3.双向实时通信websocket
* 第十四章 消息队列的整合与使用
* 14.1.消息队列与JMS规范简介
* 14.2.使用docker安装activeMQ
* 14.3.activeMQ实现点对点队列
* 14.4.activeMQ实现发布订阅队列
* 14.5.docker安装RocketMQ
* 14.6.RocketMQ实现2种消费模式
* 14.7.RocketMQ实现分布式事务
* 第十五章 邮件发送的整合与使用
* 15.1.基础协议及邮件配置整合
* 15.2.发送html和基于模板的邮件
* 15.3.发送带附件和内联附件邮件
* 第十六章 响应式框架webflux
* 16.1.webflux快速入门
* 16.2.注解方式实现restful接口
* 16.3.webflux整合mongodb
* 16.4.webclient单元测试的编写
* 番外篇:周边技术生态
* 如何使用git查看本教程代码
* centos7安装docker图文详解
* docker安装mongodb(单点)图文详解
* 如何使用mybatis自动生成的代码
';
vue深入浅出系列
最后更新于:2022-04-02 07:57:50
访问地址:[vue深入浅出系列](https://www.kancloud.cn/hanxt/vuejs2/content)
# 章节目录
* 第一章 你好,VUE
* 1.1.前端渐进式框架VUE简介
* 1.2.第一个VUE程序Hello-VUE
* 1.3.结合VUE理解MVVM模型
* 第二章 页面插值操作
* 2.1.插值表达式Mustache
* 2.2.v-html与v-pre指令的使用
* 2.3.v-text与v-cloak的使用
* 2.4.通过过滤器实现数据格式化
* 第三章 用户操作事件监听
* 3.1.v-on事件监听指令的基本用法
* 3.2.v-on事件监听方法传参
* 3.3.v-on事件监听修饰符
* 第四章 动态属性数据绑定
* 4.1.v-bind的基本用法
* 4.2.v-bind绑定calss属性(对象语法)
* 4.3.v-bind绑定class属性(数组语法)
* 4.4.v-bind绑定style属性
* 第五章 表单数据的绑定
* 5.1.v-model的基本用法
* 5.2.v-model绑定radio和checkbox
* 5.3.v-model绑定select
* 5.4.v-model的修饰符
* 第六章 属性的计算与侦听
* 6.1.计算属性computed用法
* 6.2.计算属性的set与get方法
* 6.3.属性变化的侦听watch
* 6.4.属性侦听的深入学习
* 第七章 条件判断与循环遍历
* 7.1.v-if-else与v-show条件判断
* 7.2.v-for遍历数组-对象-数值序列
* 7.3.v-for的key属性详解
* 7.4.v-for数组条件筛选
* 第八章 综合案例-订单管理
* 8.1.页面布局
* 8.2.新增订单与订单列表
* 8.3.订单数量增减与总额计算
* 8.4.订单数据查询与删除
* 第九章 js常用语法复习
* 9.1.ES6语法-var-const-let用法详解
* 9.2.ES6语法-对象的增强写法
* 9.3.ES6语法-箭头函数与this指针
* 9.4.ES6语法-变量的解构赋值
* 9.5.js数组操作
* 9.6.promise语法详解
* 第十章 组件化开发
* 10.1.组件化开发的意义
* 10.2.全局组件与局部组件
* 10.3.父子组件的数据定义及访问
* 10.4.如何实现组件的切换
* 10.5.父组件向子组件传递数据
* 10.6.子组件向父组件传播事件
* 10.7.插槽的使用场景与方法
* 第十一章 前端工程化
* 11.1.前端工程化的意义
* 11.2.前端模块化方案
* 11.3.ES6模块化方案详解
* 11.4.webpack的简介与安装
* 11.5.手搭脚手架-webpack初体验
* 11.6.手搭脚手架-webpack基础配置
* 11.7.手搭脚手架-css文件处理打包
* 11.8.手搭脚手架-图片资源打包
* 11.9.手搭脚手架-js规范兼容babel
* 11.10.手搭脚手架-webpack整合vue
* 11.11.手搭脚手架-插件与本地调试
* 11.12.手搭脚手架-环境配置分离
* 第十二章 项目脚手架VueCLI2&3
* 12.1.VueCLI脚手架简介
* 12.2.使用VueCLI2快速搭建项目
* 12.3.VueCLI2目录文件详解
* 12.4.使用VueCLI3快速搭建项目
* 12.5.VueCLI-UI界面项目配置管理
* 12.6.VueCLI3至4.0版本升级指南
* 第十三章 前端路由VueRouter
* 13.1.单页面应用与前端路由
* 13.2.安装与配置VueRouter
* 13.3.VueRouter开发第一个Demo
* 13.4.路由重定向与组件懒加载
* 13.5.嵌套路由的配置实现
* 13.6.动态路由的配置与实现
* 13.7.命名路由与命名视图
* 13.8.编程式导航
* 13.9.路由的参数传递与获取
* 第十四章 vuex应用状态管理(撰写中)
* 14.1.vuex解决的问题及使用场景
* 14.2.vuex的第一个例子
* 14.3.mutations的使用与状态跟踪
* 14.4.全局计算属性getters
* 14.5.vuex状态异步操作
* 14.6.modules模块划分
* 第十五章axios网络请求响应
* 15.1.axios简单介绍
* 15.2.axios基本用法
* 15.3.axios配置详解
* 15.4.axios的实例与拦截器
* 第十六章 综合案例-在线教育(撰写中)
* 16.1.vue实例生命周期
* 16.2.开发系统登录页面
* 16.3.开发系统登录功能(撰写中)
';
笔者其它作品推荐
最后更新于:2022-04-02 07:57:48
[vue深入浅出系列](vue%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%E7%B3%BB%E5%88%97.md)
[手摸手教你学Spring Boot2.0](%E6%89%8B%E6%91%B8%E6%89%8B%E6%95%99%E4%BD%A0%E5%AD%A6SpringBoot2.0.md)
[Spring Security-JWT-OAuth2一本通](SpringSecurity-JWT-OAuth2%E4%B8%80%E6%9C%AC%E9%80%9A.md)
[实战前后端分离RBAC权限管理系统](%E5%AE%9E%E6%88%98%E5%89%8D%E5%90%8E%E7%AB%AF%E5%88%86%E7%A6%BBRBAC%E6%9D%83%E9%99%90%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F.md)
[实战SpringCloud微服务从青铜到王者](%E5%AE%9E%E6%88%98SpringCloud%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%BB%8E%E9%9D%92%E9%93%9C%E5%88%B0%E7%8E%8B%E8%80%85.md)
';
java8-forEach(持续发布中)
最后更新于:2022-04-02 07:57:45
**Java8 forEach**是一个工具方法用于遍历集合,比如: (list, set or map) 和stream流(java8 提供的另外一个特性),然后对集合中的每一个元素执行特定的操作。
## 1. Java 8 forEach 方法
#### 1.1. Iterable.forEach()方法
下面的代码片段显示了Iterable接口forEach方法的默认实现。我们可以通过这个方法去遍历除了Map之外的所有集合类。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/cf/ce/cfcebc1645f035a9597d6f0d120f810a_464x181.png)
上面的方法对Iterable的每个元素执行操作,直到所有元素都已处理或该操作引发异常。“ action”用来表示一个接受单个输入参数且不返回结果的操作。它是“Consumer”接口的一个实例。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/f6/ac/f6acbe2b8fb14f718a7e2f3cf940e980_568x218.png)
我们可以通过实现Consumer接口的accept方法,实现自己对集合元素需要做的自定义操作。比如:下面的代码是实现集合中字符串转大写并打印出来的操作。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/c2/41/c2415cdf1368acb1c11a0d9c7888cef8_572x374.png)
#### 1.2. Map.forEach()
Map.forEach()方法对map中的每一个entry执行特定的操作,直到所有map的entry被处理完成或者抛出异常。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/e5/fa/e5fa75e1e848d00d76447bcdfba2651e_606x337.png)
使用Map.forEach() 方法
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/f6/bf/f6bff1608225b3d5923e8986efb012c0_520x325.png)
与List等集合类遍历类似,我们可以自定义一个biconsumer action去处理key-value键值对.
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/63/0f/630fee49958b3af7322278dbf8c4b71d_435x307.png)
Program output.
~~~
Key is : A
Value is : 1
Key is : B
Value is : 2
Key is : C
Value is : 3
~~~
## 2. 使用forEach遍历 List的例子
下面的代码使用forEach遍历 List中的所有偶数。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/71/ef/71ef84531ebb29008d25f61806223db5_476x180.png)
输出:
~~~
2
4
~~~
## 3. 使用forEach 遍历 Map
We already saw above program to iterate over all entries of a[HashMap](https://howtodoinjava.com/java-hashmap/)and perform an action.
We can also iterate over map keys and values and perform any action on all elements.
Java 8 forEach map entries
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/bd/9a/bd9a0e0508cac02e9b5c0196d193ed9a_590x430.png)
Program output.
~~~
A=1
B=2
C=3
A
B
C
1
2
3
~~~
';
Stream流逐行文件处理
最后更新于:2022-04-02 07:57:43
本文中为大家介绍使用java8 Stream API逐行读取文件,以及根据某些条件过滤文件内容
## 1. Java 8逐行读取文件
在此示例中,我将按行读取文件内容并在控制台打印输出。
~~~
Path filePath = Paths.get("c:/temp", "data.txt");
//try-with-resources语法,不用手动的编码关闭流
try (Stream lines = Files.lines( filePath ))
{
lines.forEach(System.out::println);
}
catch (IOException e)
{
e.printStackTrace();//只是测试用例,生产环境下不要这样做异常处理
}
~~~
上面的程序输出将在控制台中逐行打印文件的内容。
~~~
Never
store
password
except
in mind.
~~~
## 2.Java 8读取文件–过滤行
在此示例中,我们将文件内容读取为Stream。然后,我们将过滤其中包含单词"password"的所有行。
~~~
Path filePath = Paths.get("c:/temp", "data.txt");
try (Stream lines = Files.lines(filePath)){
List filteredLines = lines
.filter(s -> s.contains("password"))
.collect(Collectors.toList());
filteredLines.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();//只是测试用例,生产环境下不要这样做异常处理
}
~~~
程序输出。
~~~
password
~~~
我们将读取给定文件的内容,并检查是否有任何一行包含"password"然后将其打印出来。
## 3.Java 7 –使用FileReader读取文件
Java 7之前的版本,我们可以使用FileReader方式进行逐行读取文件。
~~~
private static void readLinesUsingFileReader() throws IOException
{
File file = new File("c:/temp/data.txt");
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
String line;
while((line = br.readLine()) != null)
{
if(line.contains("password")){
System.out.println(line);
}
}
br.close();
fr.close();
}
~~~
';
12.java8如何排序Map
最后更新于:2022-04-02 07:57:41
在这篇文章中,您将学习**如何使用Java对Map进行排序**。前几日有位朋友面试遇到了这个问题,看似很简单的问题,但是如果不仔细研究一下也是很容易让人懵圈的面试题。所以我决定写这样一篇文章。在Java中,有多种方法可以对Map进行排序,但是我们将重点介绍Java 8 Stream,这是实现目标的一种非常优雅的方法。
## 一、什么是Java 8 Stream
使用Java 8 Streams,我们可以按键和按值对映射进行排序。下面是它的工作原理:
![Java Stream函数式编程?用过都说好,案例图文详解送给你](http://cdn.zimug.com/javaStream1-2.jpg)
1. 将Map或List等集合类对象转换为Stream对象
2. 使用Streams的`sorted()`方法对其进行排序
3. 最终将其返回为`LinkedHashMap`(可以保留排序顺序)
`sorted()`方法以a`Comparator`作为参数,从而可以按任何类型的值对Map进行排序。如果对Comparator不熟悉,可以看本号前几天的文章,有一篇文章专门介绍了使用Comparator对List进行排序。
## 二、学习一下HashMap的merge()函数
在学习Map排序之前,有必要讲一下HashMap的merge()函数,该函数应用场景就是当Key重复的时候,如何处理Map的元素值。这个函数有三个参数:
* 参数一:向map里面put的键
* 参数二:向map里面put的值
* 参数三:如果键发生重复,如何处理值。可以是一个函数,也可以写成lambda表达式。
```
String k = "key";
HashMap map = new HashMap() {{
put(k, 1);
}};
map.merge(k, 2, (oldVal, newVal) -> oldVal + newVal);
```
看上面一段代码,我们首先创建了一个HashMap,并往里面放入了一个键值为k:1的元素。当我们调用merge函数,往map里面放入k:2键值对的时候,k键发生重复,就执行后面的lambda表达式。表达式的含义是:返回旧值oldVal加上新值newVal(1+2),现在map里面只有一项元素那就是k:3。
> 其实lambda表达式很简单:表示匿名函数,箭头左侧是参数,箭头右侧是函数体。函数的参数类型和返回值,由代码上下文来确定。
## 三、按Map的键排序
下面一个例子使用Java 8 Stream按Map的键进行排序:
~~~java
// 创建一个Map,并填入数据
Map codes = new HashMap<>();
codes.put("United States", 1);
codes.put("Germany", 49);
codes.put("France", 33);
codes.put("China", 86);
codes.put("Pakistan", 92);
// 按照Map的键进行排序
Map sortedMap = codes.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.collect(
Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(oldVal, newVal) -> oldVal,
LinkedHashMap::new
)
);
// 将排序后的Map打印
sortedMap.entrySet().forEach(System.out::println);
~~~
看上文中第二段代码:
* 首先使用entrySet().stream() 将Map类型转换为Stream流类型。
* 然后使用sorted方法排序,排序的依据是Map.Entry.comparingByKey(),也就是按照Map的键排序
* 最后用collect方法将Stream流转成LinkedHashMap。 其他参数都好说,重点看第三个参数,就是一个merge规则的lambda表达式,与merge方法的第三个参数的用法一致。由于本例中没有重复的key,所以新值旧值随便返回一个即可。
上面的程序将在控制台上打印以下内容,键(国家/地区名称)以自然字母顺序排序:
~~~plaintext
China=86
France=33
Germany=49
Pakistan=92
United States=1
~~~
> **请注意**使用`LinkedHashMap`来存储排序的结果以保持顺序。默认情况下,`Collectors.toMap()`返回`HashMap`。`HashMap`不能保证元素的顺序。
如果希望按照键进行逆向排序,加入下图中红色部分代码即可。
![](images/screenshot_1571800964528.png)
## 四、按Map的值排序
当然,您也可以使用Stream API按其值对Map进行排序:
~~~java
Map sortedMap2 = codes.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(oldVal, newVal) -> oldVal,
LinkedHashMap::new));
sortedMap2.entrySet().forEach(System.out::println);
~~~
这是显示Map按值排序的输出:
~~~plaintext
United States=1
France=33
Germany=49
China=86
Pakistan=92
~~~
## 五、使用TreeMap按键排序
大家可能都知道TreeMap内的元素是有顺序的,所以利用TreeMap排序也是可取的一种方法。您需要做的就是创建一个`TreeMap`对象,并将数据从`HashMap`put到`TreeMap`中,非常简单:
~~~java
// 将 `HashMap` 转为 `TreeMap`
Map sorted = new TreeMap<>(codes);
~~~
这是输出:
~~~plaintext
China=86
France=33
Germany=49
Pakistan=92
United States=1
~~~
如上所示,键(国家/地区名称)以自然字母顺序排序。
## 最后:上文代码
```
String k = "key";
HashMap map = new HashMap() {{
put(k, 1);
}};
map.merge(k, 2, (oldVal, newVal) -> oldVal + newVal);
// 创建一个Map,并填入数据
Map codes = new HashMap<>();
codes.put("United States", 1);
codes.put("Germany", 49);
codes.put("France", 33);
codes.put("China", 86);
codes.put("Pakistan", 92);
// 按照Map的键进行排序
Map sortedMap = codes.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.collect(
Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(oldVal, newVal) -> oldVal,
LinkedHashMap::new
)
);
// 将排序后的Map打印
sortedMap.entrySet().forEach(System.out::println);
// sort the map by values
Map sorted = codes.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(oldVal, newVal) -> oldVal,
LinkedHashMap::new));
sorted.entrySet().forEach(System.out::println);
```
';
11.StreamAPI终端操作
最后更新于:2022-04-02 07:57:38
## 一、Java Stream管道数据处理操作
在本号之前写过的文章中,曾经给大家介绍过 Java Stream管道流是用于简化集合类元素处理的java API。在使用的过程中分为三个阶段。在开始本文之前,我觉得仍然需要给一些新朋友介绍一下这三个阶段,如图:
![Java Stream函数式编程?用过都说好,案例图文详解送给你](http://cdn.zimug.com/javaStream1-2.jpg)
* 第一阶段(图中蓝色):将集合、数组、或行文本文件转换为java Stream管道流
* 第二阶段(图中虚线部分):管道流式数据处理操作,处理管道中的每一个元素。上一个管道中的输出元素作为下一个管道的输入元素。
* 第三阶段(图中绿色):管道流结果处理操作,也就是本文的将介绍的核心内容。
在开始学习之前,仍然有必要回顾一下我们之前给大家讲过的一个例子:
```
List nameStrs = Arrays.asList("Monkey", "Lion", "Giraffe","Lemur");
List list = nameStrs.stream()
.filter(s -> s.startsWith("L"))
.map(String::toUpperCase)
.sorted()
.collect(toList());
System.out.println(list);
```
* 首先使用stream()方法将字符串List转换为管道流Stream
* 然后进行管道数据处理操作,先用fliter函数过滤所有大写L开头的字符串,然后将管道中的字符串转换为大写字母toUpperCase,然后调用sorted方法排序。这些API的用法在本号之前的文章有介绍过。其中还使用到了lambda表达式和函数引用。
* 最后使用collect函数进行结果处理,将java Stream管道流转换为List。最终list的输出结果是:`[LEMUR, LION]`
如果你不使用java Stream管道流的话,想一想你需要多少行代码完成上面的功能呢?回到正题,这篇文章就是要给大家介绍第三阶段:对管道流处理结果都可以做哪些操作呢?下面开始吧!
## 二、ForEach和ForEachOrdered
如果我们只是希望将Stream管道流的处理结果打印出来,而不是进行类型转换,我们就可以使用forEach()方法或forEachOrdered()方法。
```
Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
.parallel()
.forEach(System.out::println);
Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
.parallel()
.forEachOrdered(System.out::println);
```
* parallel()函数表示对管道中的元素进行并行处理,而不是串行处理,这样处理速度更快。但是这样就有可能导致管道流中后面的元素先处理,前面的元素后处理,也就是元素的顺序无法保证
* forEachOrdered从名字上看就可以理解,虽然在数据处理顺序上可能无法保障,但是forEachOrdered方法可以在元素输出的顺序上保证与元素进入管道流的顺序一致。也就是下面的样子(forEach方法则无法保证这个顺序):
```
Monkey
Lion
Giraffe
Lemur
Lion
```
## 三、元素的收集collect
java Stream 最常见的用法就是:一将集合类转换成管道流,二对管道流数据处理,三将管道流处理结果在转换成集合类。那么collect()方法就为我们提供了这样的功能:将管道流处理结果在转换成集合类。
### 3.1.收集为Set
通过Collectors.toSet()方法收集Stream的处理结果,将所有元素收集到Set集合中。
```
Set collectToSet = Stream.of(
"Monkey", "Lion", "Giraffe", "Lemur", "Lion"
)
.collect(Collectors.toSet());
//最终collectToSet 中的元素是:[Monkey, Lion, Giraffe, Lemur],注意Set会去重。
```
### 3.2.收集到List
同样,可以将元素收集到`List`使用`toList()`收集器中。
```
List collectToList = Stream.of(
"Monkey", "Lion", "Giraffe", "Lemur", "Lion"
).collect(Collectors.toList());
// 最终collectToList中的元素是: [Monkey, Lion, Giraffe, Lemur, Lion]
```
### 3.3.通用的收集方式
上面为大家介绍的元素收集方式,都是专用的。比如使用Collectors.toSet()收集为Set类型集合;使用Collectors.toList()收集为List类型集合。那么,有没有一种比较通用的数据元素收集方式,将数据收集为任意的Collection接口子类型。
所以,这里就像大家介绍一种通用的元素收集方式,你可以将数据元素收集到任意的Collection类型:即向所需Collection类型提供构造函数的方式。
```
LinkedList collectToCollection = Stream.of(
"Monkey", "Lion", "Giraffe", "Lemur", "Lion"
).collect(Collectors.toCollection(LinkedList::new));
//最终collectToCollection中的元素是: [Monkey, Lion, Giraffe, Lemur, Lion]
```
注意:代码中使用了LinkedList::new,实际是调用LinkedList的构造函数,将元素收集到Linked List。当然你还可以使用诸如`LinkedHashSet::new`和`PriorityQueue::new`将数据元素收集为其他的集合类型,这样就比较通用了。
### 3.4.收集到Array
通过toArray(String[]::new)方法收集Stream的处理结果,将所有元素收集到字符串数组中。
```
String[] toArray = Stream.of(
"Monkey", "Lion", "Giraffe", "Lemur", "Lion"
) .toArray(String[]::new);
//最终toArray字符串数组中的元素是: [Monkey, Lion, Giraffe, Lemur, Lion]
```
### 3.5.收集到Map
使用Collectors.toMap()方法将数据元素收集到Map里面,但是出现一个问题:那就是管道中的元素是作为key,还是作为value。我们用到了一个Function.identity()方法,该方法很简单就是返回一个“ t -> t ”(输入就是输出的lambda表达式)。另外使用管道流处理函数`distinct()`来确保Map键值的唯一性。
```
Map toMap = Stream.of(
"Monkey", "Lion", "Giraffe", "Lemur", "Lion"
)
.distinct()
.collect(Collectors.toMap(
Function.identity(), //元素输入就是输出,作为key
s -> (int) s.chars().distinct().count()// 输入元素的不同的字母个数,作为value
));
// 最终toMap的结果是: {Monkey=6, Lion=4, Lemur=5, Giraffe=6}
```
### 3.6.分组收集groupingBy
Collectors.groupingBy用来实现元素的分组收集,下面的代码演示如何根据首字母将不同的数据元素收集到不同的List,并封装为Map。
```
Map> groupingByList = Stream.of(
"Monkey", "Lion", "Giraffe", "Lemur", "Lion"
)
.collect(Collectors.groupingBy(
s -> s.charAt(0) , //根据元素首字母分组,相同的在一组
// counting() // 加上这一行代码可以实现分组统计
));
// 最终groupingByList内的元素: {G=[Giraffe], L=[Lion, Lemur, Lion], M=[Monkey]}
//如果加上counting() ,结果是: {G=1, L=3, M=1}
```
这是该过程的说明:groupingBy第一个参数作为分组条件,第二个参数是子收集器。
![](images/screenshot_1572666789749.png)
## 四、其他常用方法
```
boolean containsTwo = IntStream.of(1, 2, 3).anyMatch(i -> i == 2);
// 判断管道中是否包含2,结果是: true
long nrOfAnimals = Stream.of(
"Monkey", "Lion", "Giraffe", "Lemur"
).count();
// 管道中元素数据总计结果nrOfAnimals: 4
int sum = IntStream.of(1, 2, 3).sum();
// 管道中元素数据累加结果sum: 6
OptionalDouble average = IntStream.of(1, 2, 3).average();
//管道中元素数据平均值average: OptionalDouble[2.0]
int max = IntStream.of(1, 2, 3).max().orElse(0);
//管道中元素数据最大值max: 3
IntSummaryStatistics statistics = IntStream.of(1, 2, 3).summaryStatistics();
// 全面的统计结果statistics: IntSummaryStatistics{count=3, sum=6, min=1, average=2.000000, max=3}
```
';
10.Stream集合元素归约
最后更新于:2022-04-02 07:57:36
Stream API为我们提供了`Stream.reduce`用来实现集合元素的归约。reduce函数有三个参数:
* *Identity标识*:一个元素,它是归约操作的初始值,如果流为空,则为默认结果。
* *Accumulator累加器*:具有两个参数的函数:归约运算的部分结果和流的下一个元素。
* *Combiner合并器(可选)*:当归约并行化时,或当累加器参数的类型与累加器实现的类型不匹配时,用于合并归约操作的部分结果的函数。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/bd/76/bd76776778618850781059c2ea5a5c8b_889x538.png)
注意观察上面的图,我们先来理解累加器:
* 阶段累加结果作为累加器的第一个参数
* 集合遍历元素作为累加器的第二个参数
## Integer类型归约
reduce初始值为0,累加器可以是lambda表达式,也可以是方法引用。
~~~
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
int result = numbers
.stream()
.reduce(0, (subtotal, element) -> subtotal + element);
System.out.println(result); //21
int result = numbers
.stream()
.reduce(0, Integer::sum);
System.out.println(result); //21
~~~
## String类型归约
不仅可以归约Integer类型,只要累加器参数类型能够匹配,可以对任何类型的集合进行归约计算。
~~~
List letters = Arrays.asList("a", "b", "c", "d", "e");
String result = letters
.stream()
.reduce("", (partialString, element) -> partialString + element);
System.out.println(result); //abcde
String result = letters
.stream()
.reduce("", String::concat);
System.out.println(result); //ancde
~~~
## 复杂对象归约
计算所有的员工的年龄总和。
~~~
Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
Employee e2 = new Employee(2,13,"F","Martina","Hengis");
Employee e3 = new Employee(3,43,"M","Ricky","Martin");
Employee e4 = new Employee(4,26,"M","Jon","Lowman");
Employee e5 = new Employee(5,19,"F","Cristine","Maria");
Employee e6 = new Employee(6,15,"M","David","Feezor");
Employee e7 = new Employee(7,68,"F","Melissa","Roy");
Employee e8 = new Employee(8,79,"M","Alex","Gussin");
Employee e9 = new Employee(9,15,"F","Neetu","Singh");
Employee e10 = new Employee(10,45,"M","Naveen","Jain");
List employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);
Integer total = employees.stream().map(Employee::getAge).reduce(0,Integer::sum);
System.out.println(total); //346
~~~
* 先用map将Stream流中的元素由Employee类型处理为Integer类型(age)。
* 然后对Stream流中的Integer类型进行归约
## Combiner合并器的使用
除了使用map函数实现类型转换后的集合归约,我们还可以用Combiner合并器来实现,这里第一次使用到了Combiner合并器。
因为Stream流中的元素是Employee,累加器的返回值是Integer,所以二者的类型不匹配。这种情况下可以使用Combiner合并器对累加器的结果进行二次归约,相当于做了类型转换。
~~~
Integer total3 = employees.stream()
.reduce(0,(totalAge,emp) -> totalAge + emp.getAge(),Integer::sum); //注意这里reduce方法有三个参数
System.out.println(total); //346
~~~
计算结果和使用map进行数据类型转换的方式是一样的。
## 并行流数据归约(使用合并器)
对于大数据量的集合元素归约计算,更能体现出Stream并行流计算的威力。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/77/5d/775d65715a631fe8d39a2861c0a3c94c_1017x563.png)
在进行并行流计算的时候,可能会将集合元素分成多个组计算。为了更快的将分组计算结果累加,可以使用合并器。
~~~
Integer total2 = employees
.parallelStream()
.map(Employee::getAge)
.reduce(0,Integer::sum,Integer::sum); //注意这里reduce方法有三个参数
System.out.println(total); //346
~~~
';
9.Stream查找与匹配元素
最后更新于:2022-04-02 07:57:34
在我们对数组或者集合类进行操作的时候,经常会遇到这样的需求,比如:
* 是否包含某一个“匹配规则”的元素
* 是否所有的元素都符合某一个“匹配规则”
* 是否所有元素都不符合某一个“匹配规则”
* 查找第一个符合“匹配规则”的元素
* 查找任意一个符合“匹配规则”的元素
这些需求如果用for循环去写的话,还是比较麻烦的,需要使用到for循环和break!本节就介绍一个如何用Stream API来实现“查找与匹配”。
## 一、对比一下有多简单
> employees是10个员工对象组成的List,在前面的章节中我们已经用过多次,这里不再列出代码。
如果我们不用Stream API实现,查找员工列表中是否包含年龄大于70的员工?代码如下:
~~~
boolean isExistAgeThan70 = false;
for(Employee employee:employees){
if(employee.getAge() > 70){
isExistAgeThan70 = true;
break;
}
}
~~~
如果我们使用Stream API就是下面的一行代码,其中使用到了我们之前学过的"谓词逻辑"。
~~~
boolean isExistAgeThan70 = employees.stream().anyMatch(Employee.ageGreaterThan70);
~~~
将谓词逻辑换成lambda表达式也可以,代码如下:
~~~
boolean isExistAgeThan72 = employees.stream().anyMatch(e -> e.getAge() > 72);
~~~
所以,我们介绍了第一个匹配规则函数:anyMatch,判断Stream流中是否包含某一个“匹配规则”的元素。这个匹配规则可以是**lambda表达式**或者**谓词**。
## 二、其他匹配规则函数介绍
* 是否所有员工的年龄都大于10岁?allMatch匹配规则函数:判断是够Stream流中的所有元素都**符合**某一个"匹配规则"。
~~~
boolean isExistAgeThan10 = employees.stream().allMatch(e -> e.getAge() > 10);
~~~
* 是否不存在小于18岁的员工?noneMatch匹配规则函数:判断是否Stream流中的所有元素都**不符合**某一个"匹配规则"。
~~~
boolean isExistAgeLess18 = employees.stream().noneMatch(e -> e.getAge() < 18);
~~~
## 三、元素查找与Optional
从列表中按照顺序查找第一个年龄大于40的员工。
~~~
Optional employeeOptional
= employees.stream().filter(e -> e.getAge() > 40).findFirst();
System.out.println(employeeOptional.get());
~~~
打印结果
~~~
Employee(id=3, age=43, gender=M, firstName=Ricky, lastName=Martin)
~~~
Optional类代表一个值存在或者不存在。在java8中引入,这样就不用返回null了。
* isPresent() 将在 Optional 包含值的时候返回 true , 否则返回 false 。
* ifPresent(Consumer block) 会在值存在的时候执行给定的代码块。我们在第3章
介绍了 Consumer 函数式接口;它让你传递一个接收 T 类型参数,并返回 void 的Lambda
表达式。
* T get() 会在值存在时返回值,否则?出一个 NoSuchElement 异常。
* T orElse(T other) 会在值存在时返回值,否则返回一个默认值。
> 关于Optinal的各种函数用法请观看视频![B站观看地址](https://www.bilibili.com/video/BV1sE411P7C1/)
* findFirst用于查找第一个符合“匹配规则”的元素,返回值为Optional
* findAny用于查找任意一个符合“匹配规则”的元素,返回值为Optional
';
8.函数式接口Comparator
最后更新于:2022-04-02 07:57:32
## 一、函数式接口是什么?
所谓的函数式接口,实际上就是接口里面**只能有一个抽象方法的接口**。我们上一节用到的Comparator接口就是一个典型的函数式接口,它只有一个抽象方法compare。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/25/5a/255a09bf972908b2366e7e93e88eefcb_1131x506.png)
只有一个抽象方法?那上图中的equals方法不是也没有函数体么?不急,和我一起往下看!
## 二、函数式接口的特点
* 接口有且仅有一个抽象方法,如上图的抽象方法compare
* 允许定义静态非抽象方法
* 允许定义默认defalut非抽象方法(default方法也是java8才有的,见下文)
* 允许java.lang.Object中的public方法,如上图的方法equals。
* FunctionInterface注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错
甚至可以说:函数式接口是专门为lambda表达式准备的,**lambda表达式是只实现接口中唯一的抽象方法的匿名实现类**。
## 三、default关键字
顺便讲一下default关键字,在java8之前
* 接口是不能有方法的实现,所有方法全都是抽象方法
* 实现接口就必须实现接口里面的所有方法
这就导致一个问题:**当一个接口有很多的实现类的时候,修改这个接口就变成了一个非常麻烦的事,需要修改这个接口的所有实现类**。
这个问题困扰了java工程师许久,不过在java8中这个问题得到了解决,没错就是default方法
* default方法可以有自己的默认实现,即有方法体。
* 接口实现类可以不去实现default方法,并且可以使用default方法。
## 四、JDK中的函数式接口举例
java.lang.Runnable,
java.util.Comparator,
java.util.concurrent.Callable
java.util.function包下的接口,如Consumer、Predicate、Supplier等
## 五、自定义Comparator排序
我们自定义一个排序器,实现compare函数(函数式接口Comparator唯一的抽象方法)。返回0表示元素相等,-1表示前一个元素小于后一个元素,1表示前一个元素大于后一个元素。这个规则和java 8之前没什么区别。
下面代码用自定义接口实现类的的方式实现:按照年龄的倒序排序!
~~~
employees.sort(new Comparator() {
@Override
public int compare(Employee em1, Employee em2) {
if(em1.getAge() == em2.getAge()){
return 0;
}
return em1.getAge() - em2.getAge() > 0 ? -1:1;
}
});
employees.forEach(System.out::println);
~~~
最终的打印结果如下,按照年龄的自定义规则进行排序。
~~~
Employee(id=8, age=79, gender=M, firstName=Alex, lastName=Gussin)
Employee(id=7, age=68, gender=F, firstName=Melissa, lastName=Roy)
Employee(id=10, age=45, gender=M, firstName=Naveen, lastName=Jain)
Employee(id=3, age=43, gender=M, firstName=Ricky, lastName=Martin)
Employee(id=4, age=26, gender=M, firstName=Jon, lastName=Lowman)
Employee(id=1, age=23, gender=M, firstName=Rick, lastName=Beethovan)
Employee(id=5, age=19, gender=F, firstName=Cristine, lastName=Maria)
Employee(id=9, age=15, gender=F, firstName=Neetu, lastName=Singh)
Employee(id=6, age=15, gender=M, firstName=David, lastName=Feezor)
Employee(id=2, age=13, gender=F, firstName=Martina, lastName=Hengis)
~~~
这段代码如果以lambda表达式简写。箭头左侧是参数,右侧是函数体,参数类型和返回值根据上下文自动判断。如下:
~~~
employees.sort((em1,em2) -> {
if(em1.getAge() == em2.getAge()){
return 0;
}
return em1.getAge() - em2.getAge() > 0 ? -1:1;
});
employees.forEach(System.out::println);
~~~
';
7.像使用SQL一样排序集合
最后更新于:2022-04-02 07:57:29
在开始之前,我先卖个关子提一个问题:我们现在有一个Employee员工类。
~~~
@Data
@AllArgsConstructor
public class Employee {
private Integer id;
private Integer age; //年龄
private String gender; //性别
private String firstName;
private String lastName;
}
~~~
你知道怎么对一个Employee对象组成的List集合,**先按照性别字段倒序排序,再按照年龄的倒序**进行排序么?如果您不知道4行代码以内的解决方案(其实是1行代码就可以实现,但笔者格式化为4行),我觉得您有必要一步步的看下去。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/47/61/476143ac65ff2f1d6bfc6b7f2c17c7e7_331x205.png)
## 一、字符串List排序
cities是一个字符串数组。**注意london的首字母是小写的。**
~~~java
List cities = Arrays.asList(
"Milan",
"london",
"San Francisco",
"Tokyo",
"New Delhi"
);
System.out.println(cities);
//[Milan, london, San Francisco, Tokyo, New Delhi]
cities.sort(String.CASE_INSENSITIVE_ORDER);
System.out.println(cities);
//[london, Milan, New Delhi, San Francisco, Tokyo]
cities.sort(Comparator.naturalOrder());
System.out.println(cities);
//[Milan, New Delhi, San Francisco, Tokyo, london]
~~~
* 当使用sort方法,按照String.CASE_INSENSITIVE_ORDER(字母大小写不敏感)的规则排序,结果是:[london, Milan, New Delhi, San Francisco, Tokyo]
* 如果使用Comparator.naturalOrder()字母自然顺序排序,结果是:[Milan, New Delhi, San Francisco, Tokyo, london]
同样我们可以把排序器Comparator用在Stream管道流中。
~~~
cities.stream().sorted(Comparator.naturalOrder()).forEach(System.out::println);
//Milan
//New Delhi
//San Francisco
//Tokyo
//london
~~~
在java 7我们是使用Collections.sort()接受一个数组参数,对数组进行排序。**在java 8之后可以直接调用集合类的sort()方法进行排序**。sort()方法的参数是一个比较器Comparator接口的实现类,Comparator接口的我们下一节再给大家介绍一下。
## 二、整数类型List排序
~~~swift
List numbers = Arrays.asList(6, 2, 1, 4, 9);
System.out.println(numbers); //[6, 2, 1, 4, 9]
numbers.sort(Comparator.naturalOrder()); //自然排序
System.out.println(numbers); //[1, 2, 4, 6, 9]
numbers.sort(Comparator.reverseOrder()); //倒序排序
System.out.println(numbers); //[9, 6, 4, 2, 1]
~~~
## 三、按对象字段对`List
';
6.Stream性能差?不要人云亦云
最后更新于:2022-04-02 07:57:27
下面是视频(优酷的清晰度有限):还是建议大家去B站观看:[B站观看地址](https://www.bilibili.com/video/BV1sE411P7C1/)。如果您觉得我做的工作对您有帮助,请去B站点赞、关注、转发、收藏,您的支持是我不竭的创作动力!
```[youku]
XNDYxMzYyODYzMg
```
## 一、粉丝的反馈
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/88/15/8815b15ca9b6236b3f92dea23eac2cc2_1076x263.png)
**问:stream比for循环慢5倍,用这个是为了啥?**
答:互联网是一个新闻泛滥的时代,三人成虎,以假乱真的事情时候发生。作为一个技术开发者,要自己去动手去做,不要人云亦云。
的确,这位粉丝说的这篇文章我也看过,我就不贴地址了,也没必要给他带流量。怎么说呢?就是一个不懂得测试的、不入流开发工程师做的性能测试,给出了一个危言耸听的结论。
## 二、所有性能测试结论都是片面的
性能测试是必要的,但针对性能测试的结果,永远要持怀疑态度。为什么这么说?
* 性能测试脱离业务场景就是片面的性能测试。你能覆盖所有的业务场景么?
* 性能测试脱离硬件环境就是片面的性能测试。你能覆盖所有的硬件环境么?
* 性能测试脱离开发人员的知识面就是片面的性能测试。你能覆盖各种开发人员奇奇怪怪的代码么?
所以,我从来不相信网上的任何性能测试的文章。凡是我自己的从事的业务场景,我都要在接近生产环境的机器上自己测试一遍。 **所有性能测试结论都是片面的,只有你生产环境下的运行结果才是真的。**
## 三、动手测试Stream的性能
### 3.1.环境
windows10 、16G内存、i7-7700HQ 2.8HZ 、64位操作系统、JDK 1.8.0_171
### 3.2.测试用例与测试结论
我们在上一节,已经讲过:
* 针对不同的数据结构,Stream流的执行效率是不一样的
* 针对不同的数据源,Stream流的执行效率也是不一样的
所以记住笔者的话:**所有性能测试结论都是片面的**,你要自己动手做,相信你自己的代码和你的环境下的测试!我的测试结果仅仅代表我自己的测试用例和测试数据结构!
#### 3.2.1.测试用例一
测试用例:5亿个int随机数,求最小值
测试结论(测试代码见后文):
* 使用普通for循环,执行效率是Stream串行流的2倍。也就是说普通for循环性能更好。
* Stream并行流计算是普通for循环执行效率的4-5倍。
* Stream并行流计算 > 普通for循环 > Stream串行流计算
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/dd/60/dd60020d6d0e0a6f0c21d40cac3fbc38_658x1710.png)
### 3.2.测试用例二
测试用例:长度为10的1000000随机字符串,求最小值
测试结论(测试代码见后文):
* 普通for循环执行效率与Stream串行流不相上下
* Stream并行流的执行效率远高于普通for循环
* Stream并行流计算 > 普通for循环 = Stream串行流计算
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/9a/25/9a255a77b70ff19563f706b14d5afb5d_652x1728.png)
### 3.3.测试用例三
测试用例:10个用户,每人200个订单。按用户统计订单的总价。
测试结论(测试代码见后文):
* Stream并行流的执行效率远高于普通for循环
* Stream串行流的执行效率大于等于普通for循环
* Stream并行流计算 > Stream串行流计算 >= 普通for循环
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/ea/d5/ead5d37c2fa78177ab100c20ce41c36f_655x1764.png)
## 四、最终测试结论
* 对于简单的数字(list-Int)遍历,普通for循环效率的确比Stream串行流执行效率高(1.5-2.5倍)。但是Stream流可以利用并行执行的方式发挥CPU的多核优势,因此并行流计算执行效率高于for循环。
* 对于list-Object类型的数据遍历,普通for循环和Stream串行流比也没有任何优势可言,更不用提Stream并行流计算。
虽然在不同的场景、不同的数据结构、不同的硬件环境下。Stream流与for循环性能测试结果差异较大,甚至发生逆转。**但是总体上而言**:
* Stream并行流计算 >> 普通for循环 ~= Stream串行流计算 (之所以用两个大于号,你细品)
* 数据容量越大,Stream流的执行效率越高。
* Stream并行流计算通常能够比较好的利用CPU的多核优势。CPU核心越多,Stream并行流计算效率越高。
stream比for循环慢5倍?也许吧,单核CPU、串行Stream的int类型数据遍历?我没试过这种场景,但是我知道这不是应用系统的核心场景。看了十几篇测试博文,和我的测试结果。我的结论是: **在大多数的核心业务场景下及常用数据结构下,Stream的执行效率比for循环更高。** 毕竟我们的业务中通常是实实在在的实体对象,没事谁总对`List`类型进行遍历?谁的生产服务器是单核?。
## 五、测试代码
~~~
com.github.houbb
junitperf
2.0.0
~~~
测试用例一:
~~~
import com.github.houbb.junitperf.core.annotation.JunitPerfConfig;
import com.github.houbb.junitperf.core.report.impl.HtmlReporter;
import org.junit.jupiter.api.BeforeAll;
import java.util.Arrays;
import java.util.Random;
public class StreamIntTest {
public static int[] arr;
@BeforeAll
public static void init() {
arr = new int[500000000]; //5亿个随机Int
randomInt(arr);
}
@JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class})
public void testIntFor() {
minIntFor(arr);
}
@JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class})
public void testIntParallelStream() {
minIntParallelStream(arr);
}
@JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class})
public void testIntStream() {
minIntStream(arr);
}
private int minIntStream(int[] arr) {
return Arrays.stream(arr).min().getAsInt();
}
private int minIntParallelStream(int[] arr) {
return Arrays.stream(arr).parallel().min().getAsInt();
}
private int minIntFor(int[] arr) {
int min = Integer.MAX_VALUE;
for (int anArr : arr) {
if (anArr < min) {
min = anArr;
}
}
return min;
}
private static void randomInt(int[] arr) {
Random r = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = r.nextInt();
}
}
}
~~~
测试用例二:
~~~
import com.github.houbb.junitperf.core.annotation.JunitPerfConfig;
import com.github.houbb.junitperf.core.report.impl.HtmlReporter;
import org.junit.jupiter.api.BeforeAll;
import java.util.ArrayList;
import java.util.Random;
public class StreamStringTest {
public static ArrayList list;
@BeforeAll
public static void init() {
list = randomStringList(1000000);
}
@JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
public void testMinStringForLoop(){
String minStr = null;
boolean first = true;
for(String str : list){
if(first){
first = false;
minStr = str;
}
if(minStr.compareTo(str)>0){
minStr = str;
}
}
}
@JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
public void textMinStringStream(){
list.stream().min(String::compareTo).get();
}
@JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
public void testMinStringParallelStream(){
list.stream().parallel().min(String::compareTo).get();
}
private static ArrayList randomStringList(int listLength){
ArrayList list = new ArrayList<>(listLength);
Random rand = new Random();
int strLength = 10;
StringBuilder buf = new StringBuilder(strLength);
for(int i=0; i orders;
@BeforeAll
public static void init() {
orders = Order.genOrders(10);
}
@JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
public void testSumOrderForLoop(){
Map map = new HashMap<>();
for(Order od : orders){
String userName = od.getUserName();
Double v;
if((v=map.get(userName)) != null){
map.put(userName, v+od.getPrice());
}else{
map.put(userName, od.getPrice());
}
}
}
@JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
public void testSumOrderStream(){
orders.stream().collect(
Collectors.groupingBy(Order::getUserName,
Collectors.summingDouble(Order::getPrice)));
}
@JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
public void testSumOrderParallelStream(){
orders.parallelStream().collect(
Collectors.groupingBy(Order::getUserName,
Collectors.summingDouble(Order::getPrice)));
}
}
class Order{
private String userName;
private double price;
private long timestamp;
public Order(String userName, double price, long timestamp) {
this.userName = userName;
this.price = price;
this.timestamp = timestamp;
}
public String getUserName() {
return userName;
}
public double getPrice() {
return price;
}
public long getTimestamp() {
return timestamp;
}
public static List genOrders(int listLength){
ArrayList list = new ArrayList<>(listLength);
Random rand = new Random();
int users = listLength/200;// 200 orders per user
users = users==0 ? listLength : users;
ArrayList userNames = new ArrayList<>(users);
for(int i=0; i
';
5.Stream的状态与并行操作
最后更新于:2022-04-02 07:57:25
下面是视频(优酷的清晰度有限):还是建议大家去B站观看:[B站观看地址](https://www.bilibili.com/video/BV1sE411P7C1/)。如果您觉得我做的工作对您有帮助,请去B站点赞、关注、转发、收藏,您的支持是我不竭的创作动力!
```[youku]
XNDYwODU2MjEwNA
```
## 一、回顾Stream管道流操作
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/87/80/8780fbf2c447fbf574591cab30cb743c_631x282.png)
通过前面章节的学习,我们应该明白了Stream管道流的基本操作。我们来回顾一下:
* 源操作:可以将数组、集合类、行文本文件转换成管道流Stream进行数据处理
* 中间操作:对Stream流中的数据进行处理,比如:过滤、数据转换等等
* 终端操作:作用就是将Stream管道流转换为其他的数据类型。这部分我们还没有讲,我们后面章节再介绍。
看下面的脑图,可以有更清晰的理解:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/78/f7/78f74991175f6ddc5c27fff109f0fc66_942x578.png)
## 二、中间操作:有状态与无状态
其实在程序员编程中,经常会接触到“有状态”,“无状态”,绝大部分的人都比较蒙。而且在不同的场景下,“状态”这个词的含义似乎有所不同。但是“万变不离其宗”,理解“状态”这个词在编程领域的含义,笔者教给大家几个关键点:
* 状态通常代表公用数据,有状态就是有“公用数据”
* 因为有公用的数据,状态通常需要额外的存储。
* 状态通常被多人、多用户、多线程、多次操作,这就涉及到状态的管理及变更操作。
是不是更蒙了?举个例子,你就明白了
* web开发session就是一种状态,访问者的多次请求关联同一个session,这个session需要存储到内存或者redis。多次请求使用同一个公用的session,这个session就是状态数据。
* vue的vuex的store就是一种状态,首先它是多组件公用的,其次是不同的组件都可以修改它,最后它需要独立于组件单独存储。所以store就是一种状态。
回到我们的Stream管道流
* filter与map操作,不需要管道流的前面后面元素相关,所以不需要额外的记录元素之间的关系。输入一个元素,获得一个结果。
* sorted是排序操作、distinct是去重操作。像这种操作都是和别的元素相关的操作,我自己无法完成整体操作。就像班级点名就是无状态的,喊到你你就答到就可以了。如果是班级同学按大小个排序,那就不是你自己的事了,你得和周围的同学比一下身高并记住,你记住的这个身高比较结果就是一种“状态”。所以这种操作就是有状态操作。
## 三、Limit与Skip管道数据截取
~~~
List limitN = Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
.limit(2)
.collect(Collectors.toList());
List skipN = Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
.skip(2)
.collect(Collectors.toList());
~~~
* limt方法传入一个整数n,用于截取管道中的前n个元素。经过管道处理之后的数据是:\[Monkey, Lion\]。
* skip方法与limit方法的使用相反,用于跳过前n个元素,截取从n到末尾的元素。经过管道处理之后的数据是: \[Giraffe, Lemur\]
## 四、Distinct元素去重
我们还可以使用distinct方法对管道中的元素去重,涉及到去重就一定涉及到元素之间的比较,distinct方法时调用Object的equals方法进行对象的比较的,如果你有自己的比较规则,可以重写equals方法。
~~~
List uniqueAnimals = Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
.distinct()
.collect(Collectors.toList());
~~~
上面代码去重之后的结果是: \["Monkey", "Lion", "Giraffe", "Lemur"\]
## 五、Sorted排序
默认的情况下,sorted是按照字母的自然顺序进行排序。如下代码的排序结果是:[Giraffe, Lemur, Lion, Monkey\],字数按顺序G在L前面,L在M前面。第一位无法区分顺序,就比较第二位字母。
~~~
List alphabeticOrder = Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
.sorted()
.collect(Collectors.toList());
~~~
排序我们后面还会给大家详细的讲一讲,所以这里暂时只做一个了解。
## 六、串行、并行与顺序
通常情况下,有状态和无状态操作不需要我们去关心。除非?:你使用了并行操作。
还是用班级按身高排队为例:班级有一个人负责排序,这个排序结果最后就会是正确的。那如果有2个、3个人负责按大小个排队呢?最后可能就乱套了。一个人只能保证自己排序的人的顺序,他无法保证其他人的排队顺序。
* 串行的好处是可以保证顺序,但是通常情况下处理速度慢一些
* 并行的好处是对于元素的处理速度快一些(通常情况下),但是顺序无法保证。这**可能会导致**进行一些**有状态操作**的时候,最后得到的不是你想要的结果。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/92/39/92394040fe48efd292feca03fa9da193_662x356.png)
```
Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
.parallel()
.forEach(System.out::println);
```
* parallel()函数表示对管道中的元素进行并行处理,而不是串行处理。但是这样就有可能导致管道流中后面的元素先处理,前面的元素后处理,也就是元素的顺序无法保证。
> 如果数据量比较小的情况下,不太能观察到,数据量大的话,就能观察到数据顺序是无法保证的。
```
Monkey
Lion
Lemur
Giraffe
Lion
```
通常情况下,parallel()能够很好的利用CPU的多核处理器,达到更好的执行效率和性能,建议使用。但是有些特殊的情况下,parallel并不适合:深入了解请看这篇文章:
[https://blog.oio.de/2016/01/22/parallel-stream-processing-in-java-8-performance-of-sequential-vs-parallel-stream-processing/](https://blog.oio.de/2016/01/22/parallel-stream-processing-in-java-8-performance-of-sequential-vs-parallel-stream-processing/)
该文章中几个观点,说明并行操作的适用场景:
* 数据源易拆分:从处理性能的角度,parallel()更适合处理ArrayList,而不是LinkedList。因为ArrayList从数据结构上讲是基于数组的,可以根据索引很容易的拆分为多个。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2d/85/2d85dbdaf34057b0fb5bf800f632b695_799x471.png)
* 适用于无状态操作:每个元素的计算都不得依赖或影响任何其他元素的计算,的运算场景。
* 基础数据源无变化:从文本文件里面边读边处理的场景,不适合parallel()并行处理。parallel()一开始就容量固定的集合,这样能够平均的拆分、同步处理。
';
4.Stream管道流的map操作
最后更新于:2022-04-02 07:57:23
下面是视频(优酷的清晰度有限):还是建议大家去B站观看:[B站观看地址](https://www.bilibili.com/video/BV1sE411P7C1/)。如果您觉得我做的工作对您有帮助,请去B站点赞、关注、转发、收藏,您的支持是我不竭的创作动力!
```[youku]
XNDU5OTE4OTA5Mg
```
## 一、回顾Stream管道流map的基础用法
最简单的需求:将集合中的每一个字符串,全部转换成大写!
~~~
List alpha = Arrays.asList("Monkey", "Lion", "Giraffe", "Lemur");
//不使用Stream管道流
List alphaUpper = new ArrayList<>();
for (String s : alpha) {
alphaUpper.add(s.toUpperCase());
}
System.out.println(alphaUpper); //[MONKEY, LION, GIRAFFE, LEMUR]
// 使用Stream管道流
List collect = alpha.stream().map(String::toUpperCase).collect(Collectors.toList());
//上面使用了方法引用,和下面的lambda表达式语法效果是一样的
//List collect = alpha.stream().map(s -> s.toUpperCase()).collect(Collectors.toList());
System.out.println(collect); //[MONKEY, LION, GIRAFFE, LEMUR]
~~~
所以**map函数的作用就是针对管道流中的每一个数据元素进行转换操作**。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/e4/b3/e4b3980b21802fab8170d9b03422f3ae_1364x632.png)
## 二、处理非字符串类型集合元素
map()函数不仅可以处理数据,还可以转换数据的类型。如下:
~~~
List lengths = alpha.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(lengths); //[6, 4, 7, 5]
~~~
~~~
Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
.mapToInt(String::length)
.forEach(System.out::println);
~~~
输出如下:
~~~
6
4
7
5
~~~
除了mapToInt。还有maoToLong,mapToDouble等等用法
## 三、再复杂一点:处理对象数据格式转换
还是使用上一节中的Employee类,创建10个对象。需求如下:
* 将每一个Employee的年龄增加一岁
* 将性别中的“M”换成“male”,F换成Female。
~~~
public static void main(String[] args){
Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
Employee e2 = new Employee(2,13,"F","Martina","Hengis");
Employee e3 = new Employee(3,43,"M","Ricky","Martin");
Employee e4 = new Employee(4,26,"M","Jon","Lowman");
Employee e5 = new Employee(5,19,"F","Cristine","Maria");
Employee e6 = new Employee(6,15,"M","David","Feezor");
Employee e7 = new Employee(7,68,"F","Melissa","Roy");
Employee e8 = new Employee(8,79,"M","Alex","Gussin");
Employee e9 = new Employee(9,15,"F","Neetu","Singh");
Employee e10 = new Employee(10,45,"M","Naveen","Jain");
List employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);
/*List maped = employees.stream()
.map(e -> {
e.setAge(e.getAge() + 1);
e.setGender(e.getGender().equals("M")?"male":"female");
return e;
}).collect(Collectors.toList());*/
List maped = employees.stream()
.peek(e -> {
e.setAge(e.getAge() + 1);
e.setGender(e.getGender().equals("M")?"male":"female");
}).collect(Collectors.toList());
System.out.println(maped);
}
~~~
由于map的参数e就是返回值,所以可以用peek函数。peek函数是一种特殊的map函数,当函数没有返回值或者参数就是返回值的时候可以使用peek函数。
## 四、flatMap
map可以对管道流中的数据进行转换操作,但是如果管道中还有管道该如何处理?即:如何处理二维数组及二维集合类。实现一个简单的需求:将“hello”,“world”两个字符串组成的集合,元素的每一个字母打印出来。如果不用Stream我们怎么写?写2层for循环,第一层遍历字符串,并且将字符串拆分成char数组,第二层for循环遍历char数组。
~~~
List words = Arrays.asList("hello", "word");
words.stream()
.map(w -> Arrays.stream(w.split(""))) //[[h,e,l,l,o],[w,o,r,l,d]]
.forEach(System.out::println);
~~~
输出打印结果:
~~~
java.util.stream.ReferencePipeline$Head@3551a94
java.util.stream.ReferencePipeline$Head@531be3c5
~~~
* 用map方法是做不到的,这个需求用map方法无法实现。map只能针对一维数组进行操作,数组里面还有数组,管道里面还有管道,它是处理不了每一个元素的。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/93/8a/938a9ceb8f8bd52a92111d1112e1b7e6_1256x524.png)
* flatMap可以理解为将若干个子管道中的数据全都,平面展开到父管道中进行处理。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/52/55/5255f59f26e472cd30314bbd1e243e73_1198x498.png)
~~~
words.stream()
.flatMap(w -> Arrays.stream(w.split(""))) // [h,e,l,l,o,w,o,r,l,d]
.forEach(System.out::println);
~~~
输出打印结果:
~~~
h
e
l
l
o
w
o
r
d
~~~
';
3.Stream的filter与谓语逻辑
最后更新于:2022-04-02 07:57:20
下面是视频(优酷的清晰度有限):还是建议大家去B站观看:[B站观看地址](https://www.bilibili.com/video/BV1sE411P7C1/)。如果您觉得我做的工作对您有帮助,请去B站点赞、关注、转发、收藏,您的支持是我不竭的创作动力!
```[youku]
XNDU5Nzk4MTgwMA
```
## 一、基础代码准备
建立一个实体类,该实体类有五个属性。下面的代码使用了lombok的注解Data、AllArgsConstructor,这样我们就不用写get、set方法和全参构造函数了。lombok会帮助我们在编译期生成这些模式化的代码。
~~~
@Data
@AllArgsConstructor
public class Employee {
private Integer id;
private Integer age; //年龄
private String gender; //性别
private String firstName;
private String lastName;
}
~~~
写一个测试类,这个测试类的内容也很简单,新建十个Employee 对象
~~~
public class StreamFilterPredicate {
public static void main(String[] args){
Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
Employee e2 = new Employee(2,13,"F","Martina","Hengis");
Employee e3 = new Employee(3,43,"M","Ricky","Martin");
Employee e4 = new Employee(4,26,"M","Jon","Lowman");
Employee e5 = new Employee(5,19,"F","Cristine","Maria");
Employee e6 = new Employee(6,15,"M","David","Feezor");
Employee e7 = new Employee(7,68,"F","Melissa","Roy");
Employee e8 = new Employee(8,79,"M","Alex","Gussin");
Employee e9 = new Employee(9,15,"F","Neetu","Singh");
Employee e10 = new Employee(10,45,"M","Naveen","Jain");
List employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);
List filtered = employees.stream()
.filter(e -> e.getAge() > 70 && e.getGender().equals("M"))
.collect(Collectors.toList());
System.out.println(filtered);
}
}
~~~
需要注意的是上面的filter传入了lambda表达式(之前的章节我们已经讲过了),表达过滤年龄大于70并且男性的Employee员工。输出如下:
~~~
[Employee(id=8, age=79, gender=M, firstName=Alex, lastName=Gussin)]
~~~
## 二、什么是谓词逻辑?
下面要说我们的重点了,通过之前的章节的讲解,我们已经知道lambda表达式表达的是一个匿名接口函数的实现。那具体到Stream.filter()中,它表达的是什么呢?看下图:可以看出它表达的是一个Predicate接口,在英语中这个单词的意思是:谓词。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/94/f0/94f0643476bef3f7d204a41e91949597_810x116.png)
### **什么是谓词?(百度百科)**
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/ca/66/ca66f27fc2b03b8135d608d00f71bfe0_519x89.png)
### 什么是谓词逻辑?
WHERE 和 AND 限定了主语employee是什么,那么WHERE和AND语句所代表的逻辑就是谓词逻辑
~~~
SELECT *
FROM employee
WHERE age > 70
AND gender = 'M'
~~~
## 三、谓词逻辑的复用
通常情况下,filter函数中lambda表达式为一次性使用的谓词逻辑。如果我们的谓词逻辑需要被多处、多场景、多代码中使用,通常将它抽取出来单独定义到它所限定的主语实体中。
比如:将下面的谓词逻辑定义在Employee实体class中。
~~~
public static Predicate ageGreaterThan70 = x -> x.getAge() >70;
public static Predicate genderM = x -> x.getGender().equals("M");
~~~
### 3.1.and语法(交集)
~~~
List filtered = employees.stream()
.filter(Employee.ageGreaterThan70.and(Employee.genderM))
.collect(Collectors.toList());
~~~
输出如下:
~~~
[Employee(id=8, age=79, gender=M, firstName=Alex, lastName=Gussin)]
~~~
### 3.2.or语法(并集)
~~~
List filtered = employees.stream()
.filter(Employee.ageGreaterThan70.or(Employee.genderM))
.collect(Collectors.toList());
~~~
输出如下:实际上就是年龄大于70的和所有的男性(由于79的那位也是男性,所以就是所有的男性)
~~~
[Employee(id=1, age=23, gender=M, firstName=Rick, lastName=Beethovan), Employee(id=3, age=43, gender=M, firstName=Ricky, lastName=Martin), Employee(id=4, age=26, gender=M, firstName=Jon, lastName=Lowman), Employee(id=6, age=15, gender=M, firstName=David, lastName=Feezor), Employee(id=8, age=79, gender=M, firstName=Alex, lastName=Gussin), Employee(id=10, age=45, gender=M, firstName=Naveen, lastName=Jain)]
~~~
### 3.3.negate语法(取反)
~~~
List filtered = employees.stream()
.filter(Employee.ageGreaterThan70.or(Employee.genderM).negate())
.collect(Collectors.toList());
~~~
输出如下:把上一小节代码的结果取反,实际上就是所有的女性
~~~
[Employee(id=2, age=13, gender=F, firstName=Martina, lastName=Hengis), Employee(id=5, age=19, gender=F, firstName=Cristine, lastName=Maria), Employee(id=7, age=68, gender=F, firstName=Melissa, lastName=Roy), Employee(id=9, age=15, gender=F, firstName=Neetu, lastName=Singh)]
~~~
';
2.初识Stream-API
最后更新于:2022-04-02 07:57:18
下面是视频(优酷的清晰度有限):还是建议大家去B站观看:[B站观看地址](https://www.bilibili.com/video/BV1sE411P7C1/)。如果您觉得我做的工作对您有帮助,请去B站点赞、关注、转发、收藏,您的支持是我不竭的创作动力!
```[youku]
XNDU5NTk4MjI4MA
```
## 一、什么是Java Stream API?
Java Stream函数式编程接口最初是在Java 8中引入的,并且与lambda一起成为Java开发的里程碑式的功能特性,它极大的方便了开放人员处理集合类数据的效率。从笔者之前看过的调查文章显示,绝大部分的开发者使用的JDK版本是java 8,其中Java Stream和lambda功不可没。
Java Stream就是一个数据流经的管道,并且在管道中对数据进行操作,然后流入下一个管道。有学过linux 管道的同学应该会很容易就理解。在没有Java Stram之前,对于集合类的操作,更多的是通过for循环。大家从后文中就能看出Java Stream相对于for 循环更加简洁、易用、快捷。
管道的功能包括:Filter(过滤)、Map(映射)、sort(排序)等,集合数据通过Java Stream管道处理之后,转化为另一组集合或数据输出。
![Java Stream函数式编程?用过都说好,案例图文详解送给你](http://cdn.zimug.com/javaStream1-2.jpg)
## 二、Stream API代替for循环
我们先来看一个例子:
```
List nameStrs = Arrays.asList("Monkey", "Lion", "Giraffe","Lemur");
List list = nameStrs.stream()
.filter(s -> s.startsWith("L"))
.map(String::toUpperCase)
.sorted()
.collect(toList());
System.out.println(list);
```
* 首先,我们使用Stream()函数,将一个List转换为管道流
* 调用filter函数过滤数组元素,过滤方法使用lambda表达式,以L开头的元素返回true被保留,其他的List元素被过滤掉
* 然后调用Map函数对管道流中每个元素进行处理,字母全部转换为大写
* 然后调用sort函数,对管道流中数据进行排序
* 最后调用collect函数toList,将管道流转换为List返回
最终的输出结果是:[LEMUR, LION]。大家可以想一想,上面的这些对数组进行遍历的代码,如果你用for循环来写,需要写多少行代码?来,我们来继续学习Java Stream吧!
## 三、将数组转换为管道流
使用Stream.of()方法,将数组转换为管道流。
```
String[] array = {"Monkey", "Lion", "Giraffe", "Lemur"};
Stream nameStrs2 = Stream.of(array);
Stream nameStrs3 = Stream.of("Monkey", "Lion", "Giraffe", "Lemur");
```
## 四、将集合类对象转换为管道流
通过调用集合类的stream()方法,将集合类对象转换为管道流。
```
List list = Arrays.asList("Monkey", "Lion", "Giraffe", "Lemur");
Stream streamFromList = list.stream();
Set set = new HashSet<>(list);
Stream streamFromSet = set.stream();
```
## 五、将文本文件转换为管道流
通过Files.lines方法将文本文件转换为管道流,下图中的Paths.get()方法作用就是获取文件,是Java NIO的API!
也就是说:我们可以很方便的使用Java Stream加载文本文件,然后逐行的对文件内容进行处理。
```
Stream lines = Files.lines(Paths.get("file.txt"));
```
';
1.lambda表达式会用了么
最后更新于:2022-04-02 07:57:16
下面是视频(优酷的清晰度有限):还是建议大家去B站观看:[B站观看地址](https://www.bilibili.com/video/BV1sE411P7C1/)。如果您觉得我做的工作对您有帮助,请去B站点赞、关注、转发、收藏,您的支持是我不竭的创作动力!
```[youku]
XNDU5NDc4OTM4MA
```
本文配套教学视频:[B站观看地址](https://www.bilibili.com/video/BV1sE411P7C1/)
在本号之前写过的一些文章中,笔者使用了lambda表达式语法,一些读者反映说代码看不懂。本以为java 13都已经出了,java 8中最重要特性lambda表达式大家应该都掌握了,实际上还是存在大量的程序员没有使用java8,还有的使用了java8也不会使用lambda表达式。
Lambda表达式是Java 8最流行最常用的功能特性。它将函数式编程概念引入Java,函数式编程的好处在于可以帮助我们节省大量的代码,非常方便易用,能够大幅度的提高我们的编码效率。在本文中,我们将介绍lambda表达式是什么,并将传统的java代码写法转换为lambda表达式写法,大家可以通过示例了解lambda表达式都对传统代码做了哪些简化。
## **一、接口定义**
首先,我们要明白lambda表达式在表达什么?答案就是lambda表达式表达接口函数的实现,所以我们有必要做一下准备工作。在传统的开发方式下,我们不习惯将代码块传递给函数。我们所有的行为定义代码都封装在方法体内,并通过对象引用执行,就像使用下面的代码一样:
```
public class LambdaDemo {
//函数定义
public void printSomething(String something) {
System.out.println(something);
}
//通过创建对象调用函数
public static void main(String[] args) {
LambdaDemo demo = new LambdaDemo();
String something = "I am learning Lambda";
demo.printSomething(something);
}
}
```
大家应该对上面的代码的开发方式不感到陌生,这是经典OOP的实现样式。下面我们对上面的代码做一个修改,创建一个功能接口,并对该接口定义抽象方法。
```
public class LambdaDemo {
//抽象功能接口
interface Printer {
void print(String val);
}
//通过参数传递功能接口
public void printSomething(String something, Printer printer) {
printer.print(something);
}
}
```
## 二、传统的接口函数实现方式
在上述实现中,Printer接口负责打印行为,可以是控制台打印,也可以是其他的打印行为。方法*printSomething*不再定义行为,而是执行*Printer*定义的行为,这样的设计更加灵活。代码如下:
```
public static void main(String[] args) {
LambdaDemo demo = new LambdaDemo();
String something = "I am using a Functional interface";
//实现Printer接口
Printer printer = new Printer() {
@Override
public void print(String val) {
//控制台打印
System.out.println(val);
}
};
demo.printSomething(something, printer);
}
```
至此我们都尚未使用lambda表达式。我们仅创建了Printer接口的具体实现,并将其传递给*printSomething*方法。
## 三、lambda表示式实现方式
关于lambda表达式概念后文再说,我们先来学习一下lambda表达式的语法:
```
(param1,param2,param3 ...,paramN)- > { //代码块; }
```
* 首先我们知道lambda表达式,表达的是接口函数
* 箭头左侧是函数的逗号分隔的形式参数列表
* 箭头右侧是函数体代码
现在,我们使用lambda表达式重构一下第一小节中的代码
```
public static void main(String[] args) {
LambdaDemo demo = new LambdaDemo();
String something = "I am learning Lambda";
//实现Printer接口(请关注下面这行lambda表达式代码)
Printer printer = (String toPrint)->{System.out.println(toPrint);};
//调用接口打印
demo.printSomething(something, printer);
}
```
lambda表达式使我们代码更简洁。实际上使用lambda表达式在性能和多核处理还有更多的好处,但是只有在理解java8 Streams API之后它们才有意义,因此不在本文讨论范围之内(本号之前的文章都有介绍)。
对比传统java代码的实现方式,代码量是不是减少了很多?但这仍然不是最简的实现方式,我们一步一步来。
```
Printer printer = (String toPrint)->{System.out.println(toPrint);};
//简化:去掉参数类型
Printer printer = (toPrint)->{System.out.println(toPrint);};
//简化:去掉参数括号
Printer printer = toPrint->{System.out.println(toPrint);};
//简化:去掉函数体花括号
Printer printer = toPrint->System.out.println(toPrint);
```
* 即使没有在箭头的左侧指定参数的类型,编译器也会从接口方法的形式参数中推断出其类型
* 当只有一个参数的时候,我们完全可以省略参数的括号
* 当函数体只有一行的时候,我们完全可以省略函数体花括号
如果我们的接口方法定义不带任何参数,则可以用空括号替换:
```
()-> System.out.println("anything you wan to print")
```
那么,我们最终通过lambda表达式,简化完成的代码是什么样的呢?庐山真面目:
```
public static void main(String[] args) {
LambdaDemo demo = new LambdaDemo();
String something="I am Lambda";
//关注下面的这行代码
demo.printSomething(something, toPrint -> System.out.println(toPrint));
}
```
我们使用lambda表达式内联为函数调用参数,将最初main方法的9行代码下降到只有3行。但笔者要说,这仍然不是lambda表达式可以完成的最终极代码简化方式,当你学习了java8 Stream API结合lambda表达式使用,你会发现你的编码效率将大幅度提高!
## **结论**
lambda表达式表达的是接口函数,箭头左侧是函数参数,箭头右侧是函数体。函数的参数类型和返回值类型都可以省略,程序会根据接口定义的上下文自动确定数据类型。
在这篇文章中,我们对Java中的Lambda表达式进行了详尽的介绍,并了解了如何使用它们来提高接口实现效率和质量。请关注本号更多有关内容,Stream API与Collections框架一起使用时为Lambda提供了更多优势。
';