JavaSE笔记——流式编程

news2024/12/24 21:09:28

文章目录

  • 前言
  • 一、从外部迭代到内部迭代
  • 二、实现机制
  • 三、常用的流操作
    • 1.collect(toList())
    • 2.map
    • 3.filter
    • 4.flatMap
    • 5.max和min
    • 6.reduce
  • 四、多次调用流操作
  • 五、高阶函数
  • 总结


前言

流是一系列与特定存储机制无关的元素——实际上,流并没有 “存储” 之说。利用流,我们无需迭代集合中的元素,就可以提取和操作它们。这些管道通常被组合在一起,在流上形成一条操作管道。

在大多数情况下,将对象存储在集合中是为了处理他们,因此你将会发现你将把编程的主要焦点从集合转移到了流上。流的一个核心好处是,它使得程序更加短小并且更易理解。当 Lambda 表达式和方法引用(method references)和流一起使用的时候会让人感觉自成一体。


一、从外部迭代到内部迭代

我们在使用集合类时,一个通用的模式是在集合上进行迭代,然后处理返回的每一个元素。比如要计算从上海来的艺术家的人数:

public class Artist {
    private String from;

    public Artist(String from) {
        this.from = from;
    }

    public String getFrom() {
        return from;
    }

    public void setFrom(String from) {
        this.from = from;
    }

    public static void main(String[] args) {
        int count = 0;
        List<Artist> artists = new ArrayList<>();
        artists.add(new Artist("上海"));
        artists.add(new Artist("成都"));
        artists.add(new Artist("北京"));

        for (Artist artist : artists) {
            if ("上海".equals(artist.getFrom())) {
                count++;
            }
        }

    }
}

尽管这样的操作可行,但存在几个问题。每次迭代集合类时,都需要写很多样板代码。将 for 循环改造成并行方式运行也很麻烦,需要修改每个 for 循环才能实现。

此外,上述代码无法流畅传达程序员的意图。for 循环的样板代码模糊了代码的本意,程序员必须阅读整个循环体才能理解。若是单一的 for 循环,倒也问题不大,但面对一个满是循环(尤其是嵌套循环)的庞大代码库时,负担就重了。

就其背后的原理来看,for 循环其实是一个封装了迭代的语法糖,我们在这里多花点时间,看看它的工作原理。首先调用 iterator 方法,产生一个新的 Iterator 对象,进而控制整个迭代过程,这就是外部迭代。迭代过程通过显式调用 Iterator 对象的 hasNext 和 next方法完成迭代。

Iterator<Artist> iterator = artists.iterator();
while(iterator.hasNext()) {
    Artist artist = iterator.next();
    if ("上海".equals(artist.getFrom())) {
        count++;
    }
}

在这里插入图片描述

另一种方法就是内部迭代,如下所示。首先要注意 stream() 方法的调用,它和上面调用 iterator() 的作用一样。该方法不是返回一个控制迭代的 Iterator 对象,而是返回内部迭代中的相应接口:Stream。

long c = artists.stream().filter(artist -> "上海".equals(artist.getFrom())).count();

在这里插入图片描述

二、实现机制

上述整个过程被分解为两种更简单的操作:过滤和计数。通常,在 Java 中调用一个方法,计算机会随即执行操作:比如,System.out.println(“Hello World”); 会在终端上输出一条信息。Stream 里的一些方法却略有不同,它们虽是普通的 Java 方法,但返回的 Stream 对象却不是一个新集合,而是创建新集合的配方。

artists.stream().filter(artist -> “上海”.equals(artist.getFrom()));

这行代码并未做什么实际性的工作,filter 只刻画出了 Stream,但没有产生新的集合。像 filter 这样只描述 Stream,最终不产生新集合的方法叫作惰性求值方法;而像 count 这样最终会从 Stream 产生值的方法叫作及早求值方法。

如果在过滤器中加入一条 println 语句,来输出艺术家的名字,就能轻而易举地看出其中的不同,艺术家名字没有被输出。

artists.stream().filter(artist -> {
    System.out.println(artist.getFrom());
    return "上海".equals(artist.getFrom());
});

在这里插入图片描述

如果将同样的输出语句加入一个拥有终止操作的流,如上面的计数操作,艺术家的名字就会被输出

artists.stream().filter(artist -> {
    System.out.println(artist.getFrom());
    return "上海".equals(artist.getFrom());
}).count();

在这里插入图片描述

判断一个操作是惰性求值还是及早求值很简单:只需看它的返回值。如果返回值是 Stream,那么是惰性求值;如果返回值是另一个值或为空,那么就是及早求值。使用这些操作的理想方式就是形成一个惰性求值的链,最后用一个及早求值的操作返回想要的结果,这正是它的合理之处。

三、常用的流操作

为了更好地理解 Stream API,掌握一些常用的 Stream 操作十分必要。除此处的几种重要操作之外,该 API 的 Javadoc 中还有更多信息。

1.collect(toList())

collect(toList()) 方法由 Stream 里的值生成一个列表,是一个及早求值操作。

Stream 的 of 方法使用一组初始值生成新的 Stream。事实上,collect 的用法不仅限于此,它是一个非常通用的强大结构,下面是使用 collect 方法的一个例子:

List<String> collected = Stream.of("a", "b", "c").collect(Collectors.toList());

这段程序展示了如何使用 collect(toList()) 方法从 Stream 中生成一个列表。由于很多 Stream 操作都是惰性求值,因此调用 Stream 上一系列方法之后,还需要最后再调用一个类似 collect 的及早求值方法。

2.map

如果有一个函数可以将一种类型的值转换成另外一种类型,map 操作就可以使用该函数,将一个流中的值转换成一个新的流。使用 map 操作将字符串转换为大写形式:

List<String> collected = Stream.of("a", "b", "c").map(s -> s.toUpperCase()).collect(Collectors.toList());

传给 map 的 Lambda 表达式只接受一个 String 类型的参数,返回一个新的 String。参数和返回值不必属于同一种类型,但是 Lambda 表达式必须是 Function 接口的一个实例,Function 接口是只包含一个参数的普通函数接口。

3.filter

遍历数据并检查其中的元素时,可尝试使用 Stream 中提供的新方法 filter 进行过滤筛选。

假设要找出一组数字中大于5的,如下展示了如何使用函数式风格编写实现代码:

List<Integer> nums = Stream.of(1, 2, 8).filter(i -> i > 5).collect(Collectors.toList());

和 map 很像,filter 接受一个函数作为参数,该函数用 Lambda 表达式表示。该函数和前面示例中 if 条件判断语句的功能一样,如果数字大于5,则返回 true。

由于此方法和 if 条件语句的功能相同,因此其返回值肯定是 true 或者 false。经过过滤,Stream 中符合条件的,即 Lambda 表达式值为 true 的元素被保留下来。该 Lambda 表达式的函数接口正是前面介绍过的 Predicate。

4.flatMap

flatMap 方法可用 Stream 替换值,然后将多个 Stream 连接成一个 Stream。
在这里插入图片描述

List<Integer> together = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4))
        .flatMap(numbers -> {
            System.out.println(numbers);
            return numbers.stream();
        })
        .collect(Collectors.toList());
System.out.println(together);

在这里插入图片描述
调用 stream 方法,将每个列表转换成 Stream 对象,其余部分由 flatMap 方法处理。flatMap 方法的相关函数接口和 map 方法的一样,都是 Function 接口,只是方法的返回值限定为 Stream 类型罢了。

5.max和min

Stream 上常用的操作之一是求最大值和最小值。Stream API 中的 max 和 min 操作足以解决这一问题。

int max = Stream.of(5, 2, 4).max(Comparator.comparingInt(o -> o)).get();
int min = Stream.of(5, 2,2, 4).min(Comparator.comparingInt(o -> o)).get();

查找 Stream 中的最大或最小元素,首先要考虑的是用什么作为排序的指标。为了让 Stream 对象按照曲大小进行排序,需要传给它一个 Comparator 对象。静态方法 comparingInt 可以方便地实现一个比较器。

6.reduce

reduce 操作可以实现从一组值中生成一个值。在上述例子中用到的 count、min 和 max 方法,因为常用而被纳入标准库中。事实上,这些方法都是 reduce 操作。

如下展示了如何通过 reduce 操作对 Stream 中的数字求和。以 0 作起点——一个空Stream 的求和结果,每一步都将 Stream 中的元素累加至 accumulator,遍历至 Stream 中的最后一个元素时,accumulator 的值就是所有元素的和。
在这里插入图片描述

int num = Stream.of(1, 2, 3).reduce(0, (acc, element) -> acc + element);

在这里插入图片描述

四、多次调用流操作

用户也可以选择每一步强制对函数求值,而不是将所有的方法调用链接在在这里插入代码片一起,但是,最好不要如此操作。错误例子:

List<Artist> musicians = Stream.of(new Artist("上海")).collect(Collectors.toList());
List<Artist> bands = musicians.stream().filter(artist -> artist.getFrom().startsWith("The")).collect(Collectors.toList());
Set<String> origins = bands.stream().map(artist -> artist.getNationality()).collect(Collectors.toSet());
  1. 代码可读性差,样板代码太多,隐藏了真正的业务逻辑;
  2. 效率差,每一步都要对流及早求值,生成新的集合;
  3. 代码充斥一堆垃圾变量,它们只用来保存中间结果,除此之外毫无用处;
  4. 难于自动并行化处理。

正确示范:

Set<String> origins = Stream.of(new Artist("上海"))
                .filter(artist -> artist.getFrom().startsWith("The"))
                .map(artist -> artist.getNationality())
                .collect(Collectors.toSet());

五、高阶函数

本文中不断出现被函数式编程程序员称为高阶函数的操作。高阶函数是指接受另外一个函数作为参数,或返回一个函数的函数。高阶函数不难辨认:看函数签名就够了。如果函数的参数列表里包含函数接口,或该函数返回一个函数接口,那么该函数就是高阶函数。

map 是一个高阶函数,因为它的 mapper 参数是一个函数。事实上,本文介绍的 Stream 接口中几乎所有的函数都是高阶函数。之前的排序例子中还用到了 comparing 函数,它接受一个函数作为参数,获取相应的值,同时返回一个 Comparator。Comparator 可能会被误认为是一个对象,但它有且只有一个抽象方法,所以实际上是一个函数接口。

事实上,可以大胆断言,Comparator 实际上应该是个函数,但是那时的 Java 只有对象,因此才造出了一个类,一个匿名类。成为对象实属巧合,函数接口向正确的方向迈出了一步。


总结

内部迭代将更多控制权交给了集合类;和 Iterator 类似,Stream 是一种内部迭代方式;将 Lambda 表达式和 Stream 上的方法结合起来,可以完成很多常见的集合操作。

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

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

相关文章

火山引擎 DataTester:如何做 A/B 实验的假设检验作者:字节跳动数据平台

A/B 实验的核心统计学理论是&#xff08;双样本&#xff09;假设检验&#xff0c;是用来判断样本与样本、样本与总体的差异是由 抽样误差 引起还是 本质差别 造成的一种统计推断方法。 假设检验&#xff0c;顾名思义&#xff0c;是一种对自己做出的假设进行数据验证的过程。通…

STM32CUBEMX_SDIO和FATFS_读写SD卡

STM32CUBEMX_SDIO和FATFS_读写SD卡 简述 FATFS是一个完全免费开源&#xff0c;专为小型嵌入式系统设计的FAT&#xff08;File Allocation Table&#xff09;文件系统模块。FATFS的编写遵循ANSI C&#xff0c;并且完全与磁盘I/O层分开。支持FAT12/FAT16/FAT32&#xff0c;支持多…

Django入门

Django 中文官网&#xff1a;初识 Django | Django 文档 | Django (djangoproject.com) Django 是一个由 Python 编写的一个开放源代码的 Web 应用框架。 使用 Django&#xff0c;只要很少的代码&#xff0c;Python 的程序开发人员就可以轻松地完成一个正式网站所需要的大部…

Python 学习笔记001-发布

Python 学习笔记001-发布Python如何发布为EXE文件发给别人装X0 我的开发环境Step 1 安装PyInstaller包Step2 打包Python文件Step 3 运行Python程序Step 4 最后附上Atm.py的代码Python如何发布为EXE文件发给别人装X 0 我的开发环境 Python : 3.10 PyCharm:2022.03 社区版 Ste…

VIAVI唯亚威光纤高分辨率多模 OTDR 测试方案

VIAVI Solutions 高分辨率多模 OTDR 测试方案设计用于飞机、宇宙飞船、潜艇和舰船中部署的超短多模光纤的特性分析和故障定位 高分辨率多模 OTDR 测试方案是业界紧凑、轻巧的便携装置。它的用户界面经过专门设计&#xff0c;简化了 OTDR 测试和结果读取。 特点 紧凑、轻巧、现…

Oracle --- 视图 索引 语法结构

目录 视图 概念 优点 创建视图 查看视图 ​修改视图 删除视图 索引 概念 分类 普通索引 唯一索引 复合索引 反向键索引 位图索引 存储函数 概念 语法结构 视图 概念 视图 是一种数据库对象&#xff0c;是从 一个或者多个 数据表或视图中导出的 虚表。 视图所…

C#,图像二值化(07)——全局阈值的迭代算法及其源代码

1、 全局阈值的迭代算法 图像阈值分割---迭代算法 (1) 为全局阈值选择一个初始估计值T(图像的平均灰度)。 (2) 用T分割图像。产生两组像素&#xff1a;G1有灰度值大于T的像素组成&#xff0c;G2有小于等于T像素组成。 (3) 计算G1和G2像素的平均灰度值m1和m2&#xff1b; (4) …

机器学习笔记之Sigmoid信念网络(二)醒眠算法

机器学习笔记之Sigmoid信念网络——醒眠算法引言回顾Sigmoid\text{Sigmoid}Sigmoid信念网络的模型表示Sigmoid\text{Sigmoid}Sigmoid信念网络——对数似然梯度求解过程中的问题醒眠算法基于平均场假设变分推断求解后验概率平均场理论求解后验的弊端醒眠算法引言 上一节介绍了对…

jsp+servlet+mysql实现的在线图书商城源码附带论文开题报告及视频指导教程

今天给大家演示的是一款由jspservletmysql实现的在线图书商城系统&#xff0c;主要分为前台后后台管理员功能&#xff0c;前台用户可以浏览查看各类图书信息&#xff0c;可自定义搜索&#xff0c;注册登录后可以将书添加到购物车&#xff0c;购物车中的商品可以提交订单&#x…

【复习笔记】JavaWeb实验重点代码

JavaWeb实验重点代码笔记 一、课上练习题目汇总&#xff08;部分&#xff09; 题目一 基础HTML、CSS和JavaScript 1.1 问题要求 页面上有下拉菜单、文本框、跳转按钮并排放置&#xff0c;当下拉菜单选中某个具体网站名称时&#xff0c;文本框出现其对应的链接地址&#xff0…

Linux环境下vs code中Markdown与PlantUML联合工作

PlantUML是一个可以让你快速编写UML图的组件。 在线服务器 https://www.plantuml.com/plantuml/uml/SyfFKj2rKt3CoKnELR1Io4ZDoSa70000 Markdown是一种轻量级标记语言&#xff0c;排版语法简洁&#xff0c;让人们更多地关注内容本身而非排版。它使用易读易写的纯文本格式编写…

零膨胀泊松回归案例分析

零膨胀泊松回归分析 计数研究模型中&#xff0c;常用泊松回归模型&#xff0c;但泊松回归模型理论上是要求平均值与标准差相等&#xff0c;如果不满足&#xff0c;则可使用负二项回归模型 在实际研究中&#xff0c;会出现一种情况即因变量为计数变量&#xff0c;并且该变量包…

Lua闭包和Upvalue上值

一、lua中的作用域 在Lua语言中声明的变量默认是全局变量&#xff0c;声明局部变量需要使用local关键字&#xff0c;和其他语言相比这有点特殊。 -- 全局变量 a 10function func()b 100 -- 仍然是全局变量local c 20 -- func的局部变量 end func()print(a b) -- 输出…

终极.NET混淆器丨.NET Reactor产品介绍

无与伦比的 .NET 代码保护系统&#xff0c;可完全阻止任何人反编译您的代码。 产品优势 01、混淆技术 .NET Reactor通过向 .NET 程序集添加不同的保护层来防止逆向工程。除了标准的混淆技术之外&#xff0c;它还包括NecroBit、虚拟化、x86代码生成或防篡改等特殊功能。NET Re…

xilinx srio ip学习笔记之初识srio

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 xilinx srio ip学习笔记之初识srio前言IP 设置总结前言 因为工作原因&#xff0c;需要对rapidio 的协议进行了解&#xff0c;在xilinx的IP核中&#xff0c;是对应着Serial R…

这支隐藏“球队”,颠覆消费品「赛场」

【潮汐商业评论/原创】 大好的黄金周末&#xff0c;Fred约了几个朋友来家里看球。按照他的计划&#xff0c;周五准备下班后&#xff0c;他赶紧得去一趟附近的大型超市扫货&#xff0c;买一批零食酒水招待朋友。没想到的是&#xff0c;好不容易等到快下班了&#xff0c;领导通知…

外包呆一年,外包的工作经历怎么写?外包的项目经验怎么写?

0. 先来看下大家的各种问题&#xff1f; 外包的工作经历怎么写&#xff1f;外包的项目经验怎么写&#xff1f;外包如何优化简历&#xff1f;进入外包后黑化了简历&#xff0c;如何成功跳出外包圈&#xff1f;外包该如何提升自己&#xff1f;外包仔如何自我救赎&#xff1f; ……

前端基础_离线Web应用概述

离线Web应用概述 在Web应用中使用缓存的原因之一是为了支持离线应用。在全球互联的时代&#xff0c;离线应用仍有其实用价值。当无法上网的时候&#xff0c;你会做什么呢&#xff1f;你可能会说如今网络无处不在&#xff0c;而且非常稳定&#xff0c;不存在没有网络的情况。但…

【服务器数据恢复】误操作导致ocfs2文件系统被格式化的数据恢复案例

服务器故障&#xff1a; 用户误操作将linux文件系统误装入到Ocfs2文件系统的数据卷上&#xff0c;导致原始Ocfs2文件系统被格式化为Ext4文件系统。 因为Ext4文件系统每隔几百兆就会写入文件系统的原始信息&#xff0c;所以本案例中的原始Ocfs2文件系统中的数据可能受到一定程度…

搭建开源版个人图床

在微博图床、gitee、jsDelivr 陆续被 ban 的今天&#xff0c;很有必要搭建自己的图床系统了。 兰空图床 兰空图床官网&#xff1a;https://www.lsky.pro docker版本&#xff1a;https://hub.docker.com/r/halcyonazure/lsky-pro-docker 本次讲解使用 docker 版本进行部署使用 …