实战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`排序 这个功能就比较有意思了,举个例子大家理解一下。 ~~~ 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); employees.sort(Comparator.comparing(Employee::getAge)); employees.forEach(System.out::println); ~~~ * 首先,我们创建了10个Employee对象,然后将它们转换为List * 然后重点的的代码:使用了函数应用Employee::getAge作为对象的排序字段,即使用员工的年龄作为排序字段 * 然后调用List的forEach方法将List排序结果打印出来,如下(当然我们重写了Employee的toString方法,不然打印结果没有意义): ~~~ Employee(id=2, age=13, gender=F, firstName=Martina, lastName=Hengis) Employee(id=6, age=15, gender=M, firstName=David, lastName=Feezor) Employee(id=9, age=15, gender=F, firstName=Neetu, lastName=Singh) Employee(id=5, age=19, gender=F, firstName=Cristine, lastName=Maria) Employee(id=1, age=23, gender=M, firstName=Rick, lastName=Beethovan) Employee(id=4, age=26, gender=M, firstName=Jon, lastName=Lowman) Employee(id=3, age=43, gender=M, firstName=Ricky, lastName=Martin) Employee(id=10, age=45, gender=M, firstName=Naveen, lastName=Jain) Employee(id=7, age=68, gender=F, firstName=Melissa, lastName=Roy) Employee(id=8, age=79, gender=M, firstName=Alex, lastName=Gussin) ~~~ * 如果我们希望List按照年龄age的倒序排序,就使用reversed()方法。如: ~~~ employees.sort(Comparator.comparing(Employee::getAge).reversed()); ~~~ ## 四、Comparator链对`List`排序 下面这段代码先是按性别的倒序排序,再按照年龄的倒序排序。 ~~~ employees.sort( Comparator.comparing(Employee::getGender) .thenComparing(Employee::getAge) .reversed() ); employees.forEach(System.out::println); //都是正序 ,不加reversed //都是倒序,最后面加一个reserved //先是倒序(加reserved),然后正序 //先是正序(加reserved),然后倒序(加reserved) ~~~ > 细心的朋友可能注意到:我们只用了一个reversed()倒序方法,这个和SQL的表述方式不太一样。这个问题不太好用语言描述,建议大家去看一下视频! 排序结果如下: ~~~ Employee(id=8, age=79, gender=M, firstName=Alex, lastName=Gussin) 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=6, age=15, gender=M, firstName=David, lastName=Feezor) Employee(id=7, age=68, gender=F, firstName=Melissa, lastName=Roy) Employee(id=5, age=19, gender=F, firstName=Cristine, lastName=Maria) Employee(id=9, age=15, gender=F, firstName=Neetu, lastName=Singh) Employee(id=2, age=13, gender=F, firstName=Martina, lastName=Hengis) ~~~ ';

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提供了更多优势。
';