《Java8实战》第4章 引入流

news2025/6/19 2:03:44

集合是 Java 中使用最多的 API。

4.1 流是什么

流是 Java API 的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。可以看作是遍历数据集的高级迭代器,而且还可以并行的处理。
例子:返回低热量菜肴名称
Java7的实现

// 用累加器筛选元素
List<Dish> lowCaloricDishes = new ArrayList<>(); 
for(Dish dish: menu) { 
 if(dish.getCalories() < 400) { 
   lowCaloricDishes.add(dish); 
 } 
} 
// 用匿名类对菜肴排序
Collections.sort(lowCaloricDishes, new Comparator<Dish>() { 
 public int compare(Dish dish1, Dish dish2) { 
   return Integer.compare(dish1.getCalories(), dish2.getCalories()); 
 } 
}); 
// 处理排序后的菜名列表
List<String> lowCaloricDishesName = new ArrayList<>(); 
for(Dish dish: lowCaloricDishes) { 
 lowCaloricDishesName.add(dish.getName()); 
} 

上面的代码还有一个“垃圾变量”lowCaloricDishes。
Java8的实现

import static java.util.Comparator.comparing; 
import static java.util.stream.Collectors.toList; 
List<String> lowCaloricDishesName = 
       menu.stream()
       .filter(d -> d.getCalories() < 400) // 选出 400 卡路里以下的菜肴
       .sorted(comparing(Dish::getCalories)) // 按照卡路里排序
       .map(Dish::getName) // 提取菜肴的名称
       .collect(toList()); // 将所有名称保存在 List 中

为了利用多核架构并行执行这段代码,你只需要把 stream()换成 parallelStream()
代码清晰可读。filter 的结果被传给了 sorted 方法,再传给 map 方法,最后传给 collect 方法。流水线一样
image.png

List<Dish> menu = Arrays.asList( 
 new Dish("pork", false, 800, Dish.Type.MEAT), 
 new Dish("beef", false, 700, Dish.Type.MEAT), 
 new Dish("chicken", false, 400, Dish.Type.MEAT), 
 new Dish("french fries", true, 530, Dish.Type.OTHER), 
 new Dish("rice", true, 350, Dish.Type.OTHER), 
 new Dish("season fruit", true, 120, Dish.Type.OTHER), 
 new Dish("pizza", true, 550, Dish.Type.OTHER), 
 new Dish("prawns", false, 300, Dish.Type.FISH), 
 new Dish("salmon", false, 450, Dish.Type.FISH) ); 
Dish 类的定义是:
public class Dish { 
 private final String name; 
 private final boolean vegetarian; 
 private final int calories; 
 private final Type type; 
 public Dish(String name, boolean vegetarian, int calories, Type type) { 
   this.name = name; 
   this.vegetarian = vegetarian; 
   this.calories = calories; 
   this.type = type; 
 } 
 public String getName() { 
   return name; 
 } 
 public boolean isVegetarian() { 
   return vegetarian; 
 } 
 public int getCalories() { 
   return calories; 
 } 
 public Type getType() { 
   return type; 
 } 
 @Override 
 public String toString() { 
   return name; 
 } 
 public enum Type { MEAT, FISH, OTHER } 
} 

接下来会谈到很多模式,比如筛选、切片、查找、匹配、映射和归约。

4.2 流简介

流简短的定义就是“从支持数据处理操作的源生成的元素序列”

  • 元素序列——就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。集合讲的是数据,流讲的是计算。
  • ——流会使用一个提供数据的源,比如集合、数组或 I/O 资源。由列表生成的流,其元素顺序与列表一致。
  • 数据处理操作——流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,比如 filter、map、reduce、find、match、sort 等。流操作可以顺序执行,也可以并行执行。
  • 流水线——很多流操作本身会返回一个流,这样多个操作就可以链接起来,构成一个更大的流水线。
  • 内部迭代——与集合使用迭代器进行显式迭代不同,流的迭代操作是在后台进行的。
import static java.util.stream.Collectors.toList; 
List<String> threeHighCaloricDishNames = 
 menu.stream() // 从 menu(菜肴列表)获得流
   .filter(dish -> dish.getCalories() > 300) // 建立操作流水线:首先选出高热量的菜肴
   .map(Dish::getName) // 获取菜名
   .limit(3) // 只选择头三个
   .collect(toList()); // 将结果保存在另一个 List 中
System.out.println(threeHighCaloricDishNames); // 结果是[pork, beef, chicken]

本例先是对 menu 调用 stream 方法,由菜单得到一个流。数据源是菜肴列表(菜单),它给流提供一个元素序列。接下来,对流应用一系列数据处理操作:filter、map、limit 和collect。除了 collect 之外,所有这些操作都会返回另一个流,这样它们就可以接成一条流水线,于是就可以看作对源的一个查询。
最后,collect 操作开始处理流水线,并返回结果(它和别的操作不一样,因为它返回的不是流,在这里是一个 List)。在调用 collect 之前,没有任何结果产生,实际上根本就没有从 menu 里选择元素。你可以这么理解:链中的方法调用都在排队等待,直到调用 collect
filter——接受一个 Lambda,从流中排除某些元素。在本例中,通过传递 Lambda d -> d.getCalories() > 300,选择出热量超过 300 卡路里的菜肴。
map——接受一个 Lambda,将元素转换成其他形式或提取信息。在本例中,通过传递方法引用 Dish::getName,相当于 Lambda d -> d.getName(),提取了每道菜的菜名。
limit——截断流,使其元素不超过给定数量。
collect——将流转换为其他形式。在本例中,流被转换为一个列表。它看起来有点儿像变魔术,第 6 章会详细解释 collect 的工作原理。现在,你可以把 collect 看作能够接受各种方案作为参数,并将流中的元素累积成为一个汇总结果的操作。这里的toList()就是将流转换为列表的方案。
image.png

4.3 流与集合

流就像是一个延迟创建的集合:只有在消费者要求的时候才会计算值
用 DVD 对比在线流媒体的例子展示了流和集合之间的差异。
image.png

4.3.1 只能遍历一次

和迭代器类似,流只能遍历一次。遍历完之后,这个流就已经被消费掉了。
流只能消费一次,多次就会抛异常

List<String> title = Arrays.asList("Modern", "Java", "In", "Action"); 
Stream<String> s = title.stream(); 
s.forEach(System.out::println); 
s.forEach(System.out::println); // 抛异常 java.lang.IllegalStateException:流已被操作或关闭

4.3.2 外部迭代与内部迭代

使用 Collection 接口需要用户去做迭代(比如用 for-each),这称为外部迭代
Stream 库使用内部迭代——它帮你把迭代做了,还把得到的流值存在了某个地方,你只要给出一个函数说要干什么就可以了。
集合:用 for-each 循环外部迭代

List<String> names = new ArrayList<>(); 
for(Dish dish: menu){ // 显式顺序迭代菜单列表
 names.add(dish.getName()); 
} 

for-each 还隐藏了迭代中的一些复杂性。for-each 结构是一个语法糖,它背后的东西用 Iterator 对象表达出来会更丑陋。
集合:用背后的迭代器做外部迭代

List<String> names = new ArrayList<>(); 
Iterator<String> iterator = menu.iterator(); 
while(iterator.hasNext()) { // 显示迭代
 Dish dish = iterator.next(); 
 names.add(dish.getName()); 
}

流:内部迭代

List<String> names = menu.stream() 
   .map(Dish::getName) // 用 getName 方法参数化 map,提取菜名
   .collect(toList()); // 开始执行操作流水线;没有迭代!

比如地上有一堆散落的玩具。需要叫孩子收拾
外部迭代一个集合,显式地取出每个项目再加以处理。我们只需要对孩子说,“把地上所有的玩具都放进盒子里”就好了。
内部迭代比较好的原因有两个:第一,孩子可以选择一只手拿娃娃,另一只手拿球;第二,她可以决定先拿离盒子最近的那个东西,然后再拿别的。
内部迭代时,项目可以透明地并行处理,或者以更优化的顺序进行处理,还可以轻松的并行执行,不需我们管理。
image.png

测验 4.1:外部迭代与内部迭代
基于你对代码清单 4-1 和代码清单 4-2 中外部迭代的学习,请选择一种流操作来重构下面的代码。
List highCaloricDishes = new ArrayList<>();
Iterator iterator = menu.iterator();
while(iterator.hasNext()) {
Dish dish = iterator.next();
if(dish.getCalories() > 300) {
highCaloricDishes.add(d.getName());
}
}
答案:应该选择使用 filter 模式。
List highCaloricDish = menu.stream() .filter(dish -> dish.getCalories() > 300) .collect(toList());

4.4 流操作

filter、map、limit这些都是中间操作,可以连成一条流水线;
collect 触发流水线执行并关闭它。
可以连接起来的流操作称为中间操作,关闭流的操作称为终端操作。
image.png

4.4.1 中间操作

除非流水线上触发一个终端操作,否则中间操作不会执行任何处理。这是因为中间操作一般都可以合并起来,在终端操作时一次性全部处理。
打印流的处理过程

List<String> names = 
 menu.stream() 
 .filter(dish -> { 
 System.out.println("filtering:" + dish.getName()); // 打印当前筛选的菜肴
 return dish.getCalories() > 300; 
 }) 
 .map(dish -> { 
 System.out.println("mapping:" + dish.getName()); // 提取菜名时打印出来
 return dish.getName(); 
 })
 .limit(3) 
 .collect(toList()); 
System.out.println(names); 

输出:
filtering:pork 
mapping:pork 
filtering:beef 
mapping:beef 
filtering:chicken 
mapping:chicken 
[pork, beef, chicken] 

尽管很多菜的热量都高于 300 卡路里,但只选出了前三个!这是因为 limit 操作和一种称为短路的技巧,
尽管 filter 和 map 是两个独立的操作,但它们合并到同一次遍历中了(我们把这种技术叫作循环合并

4.4.2 终端操作

终端操作会从流的流水线生成结果,其结果是任何不是流的值,比如 List、Integer,甚至 void。

测验 4.2:中间操作与终端操作
在下列流水线中,你能找出中间操作和终端操作吗?
long count = menu.stream()
.filter(dish -> dish.getCalories() > 300)
.distinct()
.limit(3)
.count();
答案:流水线中最后一个操作 count 返回一个 long,这是一个非 Stream 的值。因此它是一个终端操作。所有前面的操作,filter、distinct、limit,都是连接起来的,并返回一个 Stream,因此它们是中间操作。

4.4.3 使用流

流的使用一般包括三件事:

  • 一个数据源(如集合)来执行一个查询;
  • 一个中间操作链,形成一条流的流水线;
  • 一个终端操作,执行流水线,并能生成结果。

image.png

4.5 路线图

4.6 小结

  • 流是“从支持数据处理操作的源生成的一系列元素”。
  • 流利用内部迭代:迭代通过 filter、map、sorted 等操作被抽象掉了。
  • 流操作有两类:中间操作和终端操作。
  • filter 和 map 等中间操作会返回一个流,并可以链接在一起。可以用它们来设置一条流水线,但并不会生成任何结果。
  • forEach 和 count 等终端操作会返回一个非流的值,并处理流水线以返回结果。
  • 流中的元素是按需计算的。

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

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

相关文章

语音识别实战(python代码)(一)

语音识别实战 &#xff08;python &#xff1a;pyttsx、SAPI、SpeechLib实例代码&#xff09;(一&#xff09; 本文目录&#xff1a; 一、语音识别的基本原理 &#xff08;1&#xff09;、语音识别的起源与发展 &#xff08;2&#xff09;、语音识别的基本原理 &#xff0…

吸烟行为检测系统(Python+YOLOv5深度学习模型+清新界面)

摘要&#xff1a;吸烟行为检测软件用于日常场景下吸烟行为监测&#xff0c;快速准确识别和定位吸烟位置、记录并显示检测结果&#xff0c;辅助公共场所吸烟安全报警等。本文详细介绍吸烟行为检测系统&#xff0c;在介绍算法原理的同时&#xff0c;给出Python的实现代码、训练数…

BGA封装与PCB差分互连结构的设计与优化

摘要&#xff1a;随着电子系统通信速率的不断提升&#xff0c;BGA封装与PCB互连区域的信号完整性问题越来越突出。 针对高速BGA封装与PCB差分互连结构进行设计与优化&#xff0c;着重分析封装与PCB互连区域差分布线方式&#xff0c;信号布局方式&#xff0c;信号孔/地孔比&…

Unity编写Shader内置各种矩阵和方法介绍

返回目录 大家好&#xff0c;我是阿赵。 这里记录一下Unity编写Shader内置各种矩阵和方法 一、Unity内置转换矩阵 1、MVP类矩阵 UNITY_MATRIX_MVP:Current model * view * projection matrix. UNITY_MATRIX_MV:Current model * view matrix. UNITY_MATRIX_V:Current view m…

静态库与动态库

库是已经写好的、成熟的、可复用的代码。在我们的开发的应用中经常有一些公共代码是需要反复使用的&#xff0c;就把这些代码编译为库文件。库可以简单看成一组目标文件的集合&#xff0c;将这些目标文件经过压缩打包之后形成的一个可执行代码的二进制文件。库有两种&#xff1…

uniapp页面后退时更改页面内容【uniapp如何区分页面是跳转来的还是后退来的】【伸手党福利】

目录应用场景实现目标分析技术难点解决方法另附&#xff1a;自动登录判断跳转页面ps2 这个案例的实际简单的解决方法应用场景 建立一个自动登录的中间页&#xff0c;如果自动登录&#xff0c;则自动跳转到内部应用。如果自动登录失败&#xff0c;则显示用户名密码输入页。 发现…

文心一言对于宣传文案理解

前言 前段时间对于文心一言开放部分内测邀请&#xff0c;有幸获得邀请内测权限&#xff01;抱着试一试的态度对其进行了使用&#xff0c;结果还是比较满意的。我们来看一下我所说的满意是否能够达到你的要求&#xff01;&#xff01;&#xff01; 使用逻辑 文心一言的使用还…

静态路由的原理和配置(理论详细实验全面)

第五章&#xff1a;静态路由 目录 第五章&#xff1a;静态路由 5.1路由器的工作原理 5.1.1路由器根据路由表转发数据 5.1.2 路由信息获取的方式 5.2路由选路原则 5.2.1最长匹配原则 5.2.2路由优先级 5.2.3路由度量值 5.3静态路由 5.3.1静态路由实验 5.3.2缺省路由实…

多模态模型技术综述

多模态架构导语1. Image2Text1.1 图像数据集准备1.2 图像to文本的生成模型1.2.1 M2 模型&#xff08;Meshed—Memory Transformer&#xff09;Memory-Augmented EncoderMeshed Decoder2. text2Image2.1 生成对抗网络&#xff08;GAN&#xff09;2.1.1 文本生成图像基础GAN2.1.2…

4.1 不定积分的概念与性质

思维导图&#xff1a; 学习目标&#xff1a; 学习不定积分&#xff0c;我会采取以下几个步骤&#xff1a; 1.学习基本的积分表&#xff1a;首先&#xff0c;我会学习基本的积分公式&#xff0c;例如幂函数、指数函数、三角函数、反三角函数等的积分公式。这些公式是不定积分…

enote笔记法之附录1——“语法词”(即“关联词”)(ver0.22)

章节&#xff1a;enote笔记法之附录1——“语法词”&#xff08;即“关联词”&#xff09;&#xff08;ver0.22&#xff09; 上面的是截屏的完整版&#xff0c;分割线下面的是纯文字版本&#xff1a; 作者姓名&#xff08;本人的真实姓名&#xff09;&#xff1a;胡佳吉 居…

计及需求侧响应日前、日内两阶段鲁棒备用优化(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

机器学习实战:Python基于K近邻KNN进行分类预测(四)

文章目录1 前言1.1 K近邻的介绍1.2 K近邻的应用2 二维数据集演示2.1 导入函数2.2 导入数据2.3 训练模型及可视化3 莺尾花数据集全数据演示3.1 导入函数3.2 导入数据3.3 训练模型及预测4 模拟数据集演示4.1 导入函数4.2 模拟数据集4.3 建模比较5 马绞痛数据pipeline演示5.1 下载…

App 自动化测试

一、移动端测试基础 1 移动端自动化环境搭建 1.1 java安装 1.2 Android SDK安装 SDK (Software Development Kit) 软件开发工具包是软件开发工程师用于为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件的开发工具的集合。Android SDK 就是 Android 专属的软件开…

ERTEC200P-2 PROFINET设备完全开发手册(5-1)

5.1 非周期通讯 非周期通讯&#xff0c;顾名思义&#xff0c;表示这种通讯并不是在每个通讯周期都要进行的通讯&#xff0c;也叫做异步通讯或者非实时通讯。非周期通讯访问的数据叫做数据记录&#xff08;record data&#xff09;。一般情况下&#xff0c;以下三个名词表示的是…

ubuntu下常用命令(嵌入式)

开发环境: ubuntu-14.04.1-desktop-i386 VMware-workstation-full-10.0.2-1744117.1398244508.exe lsb_release -a 查看ubuntu版本 ctrlalta 打开控制台 du -sh 文件/文件夹 查看文件或文件夹的大小 su 或 su root 都是切换到root账户 su 普通用户 切换到普…

CSS2023年面试题汇总~~~~持续更新中!!!!

文章目录1.元素水平垂直居中的方法有哪些&#xff1f;2.flex布局3.position定位4.display:none与visibility:hidden的区别1.元素水平垂直居中的方法有哪些&#xff1f; 利用定位margin:auto利用定位margin:负值利用定位transformtable布局flex布局grid布局 其中2&#xff0c;…

风电的Weibull分布及光电的Beta分布组合研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

二、线程的Thread 类及常见方法【2/12】【多线程】

线程的Thread 类及常见方法2. Thread 类及常见方法2.1 Thread 的常见构造方法2.2 Thread 的几个常见属性2.3 启动一个线程-start()★★★start和run的区别★★★run不会创建线程 是在原来的基础上 执行代码start 创建线程&#xff0c;在新的线程 执行代码2.4 中断一个线程★★★…

常见面试题之Redis篇

1.1.Redis与Memcache的区别&#xff1f; redis支持更丰富的数据类型&#xff08;支持更复杂的应用场景&#xff09;&#xff1a;Redis不仅仅支持简单的k/v类型的数据&#xff0c;同时还提供list&#xff0c;set&#xff0c;zset&#xff0c;hash等数据结构的存储。memcache支持…