Java8实战-总结26

news2024/11/27 1:46:06

Java8实战-总结26

  • 用流收集数据
    • 分组
      • 多级分组
      • 按子组收集数据

用流收集数据

分组

一个常见的数据库操作是根据一个或多个属性对集合中的项目进行分组。就像前面讲到按货币对交易进行分组的例子一样,如果用指令式风格来实现的话,这个操作可能会很麻烦、啰嗦而且容易出错。但是,如果用Java 8所推崇的函数式风格来重写的话,就很容易转化为一个非常容易看懂的语句。来看看这个功能的第二个例子:假设要把菜单中的菜按照类型进行分类,有肉的放一组,有鱼的放一组,其他的都放另一组。用Collectors.groupingBy工厂方法返回的收集器就可以轻松地完成这项任务,如下所示:

Map<Dish.Type,List<Dish>> dishesByType = menu.stream().collect(groupingBy(Dish::getType));

其结果是下面的Map:

{FISH = [prawns, salmon], OTHER=[french fries, rice, season fruit, pizza], MEAT=[pork, beef, chicken]}

这里,给groupingBy方法传递了一个Function(以方法引用的形式),它提取了流中每一道DishDish.Type。这个Function叫作分类函数,因为它用来把流中的元素分成不同的组。如下图所示,分组操作的结果是一个Map,把分组函数返回的值作为映射的键,把流中所有具有这个分类值的项目的列表作为对应的映射值。在菜单分类的例子中,键就是菜的类型,值就是包含所有对应类型的菜肴的列表。
在这里插入图片描述
但是,分类函数不一定像方法引用那样可用,因为想用以分类的条件可能比简单的属性访问器要复杂。例如,把热量不到400卡路里的菜划分为“低热量”(diet),热量400到700卡路里的菜划为“普通”(normal),高于700卡路里的划为“高热量”(fat)。由于Dish类的作者没有把这个操作写成一个方法,无法使用方法引用,但可以把这个逻辑写成Lambda表达式:

public enum CaloricLevel { DIET, NORMAL, FAT }
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream()
		.collect(groupingBy(dish -> {
				if(dish.getCalories() <= 400)return CaloricLevel.DIET;
				else if (dish.getcalories()<= 700) return CaloricLevel.NORMAL;
				else return CaloricLevel.FAT;
		}));

现在,已经看到了如何对菜单中的菜肴按照类型和热量进行分组,但要是想同时按照这两个标准分类怎么办?分组的强大之处就在于它可以有效地组合。

多级分组

要实现多级分组,可以使用一个由双参数版本的Collectors.groupingBy工厂方法创建的收集器,它除了普通的分类函数之外,还可以接受collector类型的第二个参数。那么要进行二级分组的话,可以把一个内层groupingBy传递给外层groupingBy,并定义一个为流中项目分类的二级标准,如下多级分组代码示例所示:

Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel = menu.stream().collect(
	//一级分类函数
	groupingBy(Dish::getType,
	 groupingBy(dish -> {//二级分类函数
		if(dish.getCalories() <= 400) return CaloricLevel.DIET;
		else if (dish.getcalories() <= 700) return CaloricLevel.NORMAL;
		else return CaloricLevel.FAT;
		})
	)
);

这个二级分组的结果就是像下面这样的两级Map:

{MEAT={DIET=[chicken],NORMAL=[beef],FAT=[pork]},
FISH={DIET=[prawns],NORMAL=[salmon]},
OTHER={DIET=[rice, seasonal fruit],NORMAL=[french fries, pizza]}}

这里的外层Map的键就是第一级分类函数生成的值:"fish,meat,other",而这个Map的值又是一个Map,键是二级分类函数生成的值:“normal,diet,fat”。最后,第二级map的值是流中元素构成的List,是分别应用第一级和第二级分类函数所得到的对应第一级和第二级键的值:“salmon、pizza.”这种多级分组操作可以扩展至任意层级,n级分组就会得到一个代表n级树形结构的nMap
下图显示了为什么结构相当于n维表格,并强调了分组操作的分类目的。

一般来说,把groupingBy看作“桶”比较容易明白。第一个groupingBy给每个键建立了一个桶。然后再用下游的收集器去收集每个桶中的元素,以此得到n级分组。
在这里插入图片描述

按子组收集数据

可以把第二个groupingBy收集器传递给外层收集器来实现多级分组。但进一步说,传递给第一个groupingBy的第二个收集器可以是任何类型,而不一定是另一个groupingBy。例如,要数一数菜单中每类菜有多少个,可以传递counting收集器作为groupingBy收集器的第二个参数:

Map<Dish.Type, Long> typesCount = menu.stream().collect(groupingBy(Dish::getType, counting()));

其结果是下面的Map:

{MEAT = 3, FISH = 2, OTHER = 4}

还要注意,普通的单参数groupingBy(f)(其中f是分类函数)实际上是groupingBy (f, toList())的简便写法。

再举一个例子,可以把前面用于查找菜单中热量最高的菜肴的收集器改一改,按照菜的类型分类:

Map<Dish.Type, Optional<Dish>> mostcaloricByType = menu.stream().collect (groupingBy(Dish::getType, maxBy(comparingInt(Dish::getcalories))));

这个分组的结果显然是一个map,以Dish的类型作为键,以包装了该类型中热量最高的Dishoptional<Dish>作为值:

{FISH = Optional[salmon], OTHER = Optional[pizza], MEAT = Optional[pork]}

注意 这个Map中的值是optional,因为这是maxBy工厂方法生成的收集器的类型,但实际上,如果菜单中没有某一类型的Dish,这个类型就不会对应一个optional.empty()值,而且根本不会出现在Map的键中。groupingBy收集器只有在应用分组条件后,第一次在流中找到某个键对应的元素时才会把键加入分组Map中。这意味着optional包装器在这里不是很有用,因为它不会仅仅因为它是归约收集器的返回类型而表达一个最终可能不存在却意外存在的值。

1、把收集器的结果转换为另一种类型

因为分组操作的Map结果中的每个值上包装的optional没什么用,所以可能想要把它们去掉。要做到这一点,或者更一般地来说,把收集器返回的结果转换为另一种类型,可以使用Collectors.collectingAndThen工厂方法返回的收集器,如下所示查找每个子组中热量最高的Dish:

Map<Dish.Type, Dish> mostcaloricByType = menu.stream()
		//分类函数
		.collect (groupingBy(Dish::getType, 
			collectingAndThen(
				maxBy(comparingInt(Dish::getcalories)),//包装后的收集器
				//转换函数
				Optional::get)));

这个工厂方法接受两个参数——要转换的收集器以及转换函数,并返回另一个收集器。这个收集器相当于旧收集器的一个包装,collect操作的最后一步就是将返回值用转换函数做一个映射。在这里,被包起来的收集器就是用maxBy建立的那个,而转换函数optional::get则把返回的optional中的值提取出来。前面已经说过,这个操作放在这里是安全的,因为reducing收集器永远都不会返回optional.empty()。其结果是下面的Map:

{FISH=salmon, OTHER=pizza,  MEAT=pork}

把好几个收集器嵌套起来很常见,它们之间到底发生了什么可能不那么明显。下图可以直观地展示它们是怎么工作的。从最外层开始逐层向里,注意以下几点。

  • 收集器用虚线表示,因此groupingBy是最外层,根据菜肴的类型把菜单流分组,得到三个子流。
  • groupingBy收集器包裹着collectingAndThen收集器,因此分组操作得到的每个子流 都用这第二个收集器做进一步归约。
  • collectingAndThen收集器又包裹着第三个收集器maxBy
  • 随后由归约收集器进行子流的归约操作,然后包含它的collectingAndThen收集器会对其结果应用optional:get转换函数。
  • 对三个子流分别执行这一过程并转换而得到的三个值,也就是各个类型中热量最高的Dish,将成为groupingBy收集器返回的Map中与各个分类键(Dish的类型)相关联的值。

2、与groupingBy联合使用的其他收集器的例子
一般来说,通过groupingBy工厂方法的第二个参数传递的收集器将会对分到同一组中的所有流元素执行进一步归约操作。例如,重用求出所有菜肴热量总和的收集器,不过这次是对每一组Dish求和:

Map<Dish.Type, Integer> totalcaloriesByType = menu.stream().collect(groupingBy (Dish::getType, summingInt(Dish::getcalories)));

然而常常和groupingBy联合使用的另一个收集器是mapping方法生成的。这个方法接受两个参数:一个函数对流中的元素做变换,另一个则将变换的结果对象收集起来。其目的是在累加之前对每个输入元素应用一个映射函数,这样就可以让接受特定类型元素的收集器适应不同类型的对象。一个使用这个收集器的实际例子:比方说想要知道,对于每种类型的Dish,菜单中都有哪些CaloricLevel。可以把groupingBymapping收集器结合起来,如下所示:

Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType = menu.stream().collect(
groupingBy(Dish::getType, mapping(
dish -> {if (dish.getcalories() <= 400)return CaloricLevel.DIET;
		else if(dish.getCalories() <= 700) return CaloricLevel.NORMAL;
		else return CaloricLevel.FAT;},
toset())));

在这里插入图片描述
这里,就像前面见到过的,传递给映射方法的转换函数将Dish映射成了它的CaloricLevel:生成的CaloricLevel流传递给一个toSet收集器,它和toList类似,不过是把流中的元素累积到一个Set而不是List中,以便仅保留各不相同的值。如先前的示例所示,这个映射收集器将会收集分组函数生成的各个子流中的元素,让你得到这样的Map结果:

{OTHER=[DIET, NORMAL], MEAT=[DIET, NORMAL,FAT], PISH=[DIET, NORMAL]}

由此就可以轻松地做出选择了。在上一个示例中,对于返回的Set是什么类型并没有任何保证。但通过使用toCollection,就可以有更多的控制。例如,可以给它传递一个构造函数引用来要求HashSet:

Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType =menu.stream().collect(
		groupingBy(Dish::getType, mapping(
		dish -> { if(dish.getcalories() <= 400) return CaloricLeve1.DIET;
				else if (dish.getcalories() <= 700) return CaloricLevel.NORMAL;
				else return CaloricLevel.FAT;},
			toCollection(Hashset::new))));

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1005002.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ABB PP846 3BSE042238R1触摸屏模块

ABB PP846 3BSE042238R1是一款触摸屏模块&#xff0c;通常用于工业自动化和控制系统中&#xff0c;以提供人机界面&#xff08;HMI&#xff09;功能&#xff0c;允许操作员与控制系统进行交互和监控。以下是一些可能的产品功能和功能&#xff0c;但请注意&#xff0c;具体的功能…

win10一进桌面就全部黑屏了 win10开机黑屏怎么办

在使用Windows 10的过程中&#xff0c;我们会遇到许多问题&#xff0c;但它们都不会像黑屏那样烦人&#xff0c;因为黑屏时不会收到任何错误代码或消息&#xff0c;提示我们从何处开始进行故障排除。 在Windows 10操作系统中&#xff0c;出现黑屏可能有多种原因&#xff0c;它…

性能测试度量指标

1-响应时间 响应时间指从用户或事务在客户端发起一个请求开始&#xff0c;到客户端接收到从服务器端返回的响应结束&#xff0c;这整个过程所消耗的时间 在性能测试实践中&#xff0c;为了使响应时间更具代表性&#xff0c;响应时间通常是指事务的平均响应时间ART 在实践中要…

力扣刷题-移除指定值的链表元素

力扣203移除元素 题目来源&#xff1a; 力扣203 题目描述&#xff1a; 非常简单的一道题&#xff0c;主要强调两点 链表删除要记录删除位置的前驱节点 头节点没有前驱 因此直接headhead.next为了保持与后两种一致&#xff0c;加上虚拟节点&#xff0c;下一节点指向头节点 /***…

Java 复习笔记 -学生管理系统进阶篇

文章目录 学生管理系统进阶版一&#xff0c;需求部分&#xff08;一&#xff09;需求&#xff08;二&#xff09;分析1&#xff0c;登录界面2&#xff0c;用户类3&#xff0c;注册功能4&#xff0c;登录功能5&#xff0c;忘记密码6&#xff0c;验证码规则 二&#xff0c;实现部…

【具身智能】RT-2:视觉-语言-动作模型(VLA)

文章目录 前言一、视觉-语言-动作(VLA)模型二、利用 VLM 控制机器人三、实验四、 Demo五、总结前言 Robotic Transformer 2(RT-2)是由谷歌 DeepMind 新推出的大语言模型,它为人类提供了通过纯语言命令来优化机器人控制的能力。与此前的大模型不同,RT-2是一种新型的视觉-…

使用C语言EasyX 创建动态爱心背景

简介 在计算机图形学的世界中&#xff0c;有很多方法可以使程序的界面更加吸引人。在本篇博客中&#xff0c;我将向大家介绍如何使用 EasyX 图形库在 C 中创建一个动态的爱心背景。这不仅是一个简单的动画效果&#xff0c;它还包括背景的星星、旋转的心形以及一个美观的背景渐…

Scrapy爬虫框架实战

Python实现爬虫是很容易的&#xff0c;一般来说就是获取目标网站的页面&#xff0c;对目标页面的分析、解析、识别&#xff0c;提取有用的信息&#xff0c;然后该入库的入库&#xff0c;该下载的下载。以前写过一篇文章《Python爬虫获取电子书资源实战》&#xff0c;以一个电子…

Vite+React+Electron开发入门,10分钟搭建本地环境并打包

前言 想使用vite和react开发跨平台桌面的软件方案有electron和tauri两种&#xff0c;但是我个人更喜欢tauri&#xff0c;无奈electron名声大燥&#xff0c;面试要求里很多都写着&#xff1a;electron...可见这类公司多么落后。但是呢&#xff0c;又秉持着存在即合理的理念&…

Android 12 源码分析 —— 应用层 五(SystemUI的StatusBar类的启动过程和三个窗口的创建)

Android 12 源码分析 —— 应用层 五&#xff08;SystemUI的StatusBar类的启动过程和三个窗口的创建&#xff09; 在前面的文章中&#xff0c;我们介绍了SystemUI App的基本布局和基本概念。接下来&#xff0c;我们进入SystemUI应用的各个UI是如何被加入屏幕的。那么我们就先从…

计算机视觉实战项目(图像分类+目标检测+目标跟踪+姿态识别+车道线识别+车牌识别)

图像分类 教程博客_传送门链接:链接 在本教程中&#xff0c;您将学习如何使用迁移学习训练卷积神经网络以进行图像分类。您可以在 cs231n 上阅读有关迁移学习的更多信息。 本文主要目的是教会你如何自己搭建分类模型&#xff0c;耐心看完&#xff0c;相信会有很大收获。废话不…

Linux——Shell脚本编程(1)

一、为什么要学习 Shell 编程 &#xff1f; 1)Linux运维工程师在进行服务器集群管理时&#xff0c;需要编写Shell程序来进行服务器管理。 2)对于 JavaEE 和 Python 程序员来说&#xff0c;工作的需要&#xff0c;要求你编写一些 Shell脚本进行程序或者是服务器的维护&#xff…

【Linux学习笔记】基础命令2

1. rmdir指令 && rm指令1.1. 基础概念1.2. 命令用法1.2.1. rmdir命令1.2.2. rm命令1.2.3. rm命令的注意事项 2. man命令3. cp指令3.1. cp指令基础概念3.2. cp命令的用法 4. mv命令5. cat命令6. more命令 && less命令6.1. more命令6.2. less命令 7. head命令和t…

C#下使用IronPython来实现热更新

问题 之前我们学习过Roslyn&#xff0c;他可以动态编译代码并运行&#xff0c;然后通过ALC加载即插即用&#xff0c;但是遇到一些问题感觉无法解决&#xff0c;我编写一个类A在ALC中&#xff0c;另外一个类B要实例化这个A&#xff0c;我想让他们都能灵活卸载&#xff0c;但是如…

MySQL与ES数据同步的四种方案及实践演示

文章目录 一、同步双写优点缺点双写失败风险项目演示 二、异步双写&#xff08;MQ方式&#xff09;优点缺点项目演示 三、基于Datax同步核心组件架构图支持的数据源及操作项目演示 四、基于Binlog实时同步实现原理优点缺点项目演示 一、同步双写 也就是同步调用&#xff0c;这…

IPIDEA动态代理IP更适合于哪些业务场景?为什么动态代理IP更经济实惠?

动态代理IP是一种非常有用的工具&#xff0c;在许多业务场景中发挥重要作用。动态代理IP可以帮助用户提高网络速度和稳定性&#xff0c;提高工作效率&#xff0c;对于需要进行跨境业务的企业和个人来说尤为重要。 让我们先来看看动态代理IP更适合于哪些业务场景。 1.数据采集…

StraUML的详细使用步骤

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于JRebel & XRebel的相关操作吧 下载和安装&#xff1a;首先&#xff0c;你需要从StarUML的官方网站或适用于你操作系统的应用商店下载并安装StarUML软件Sta…

人工智能:神经细胞模型到神经网络模型

人工智能领域中的重要流派之一是&#xff1a;从神经细胞模型&#xff08;Neural Cell Model&#xff09;到神经网络模型&#xff08;Neural Network Model&#xff09;。 一、神经细胞模型 第一个人工神经细胞模型是“MP”模型&#xff0c;它是由麦卡洛克、匹茨合作&#xff0…

基于spingboot的websocket订阅、广播、多人聊天室示例

概述 基于spingboot的websocket多人聊天系统。包括订阅&#xff0c;广播、点对点单人聊天&#xff0c;多人聊天室功能。 详细 一、运行效果 简单示例 广播 单人聊天 多人聊天室 二、相关代码 websocket配置 package com.iamgpj.demowebsocket.config;import com.iamgpj.d…

更大的数据库,更多的分析内容!凌恩明星产品鱼类eDNA产品再次大升级!!

喜大普奔&#xff0c;凌恩生物明星产品鱼类eDNA产品再次大升级&#xff01;自建鱼类数据库&#xff0c;本次升级获得了更大的数据库&#xff0c;更全面的物种分类&#xff0c;更多的分析内容&#xff0c;鱼类物种检测更加精准&#xff01;&#xff01; eDNA宏条形码技术在鱼类…