JavaSE笔记——函数式编程(流)

news2024/12/31 6:22:36

文章目录

  • 前言
  • 一、从外部迭代到内部迭代
  • 二、实现机制
  • 三、常用的流操作
    • 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/127948.html

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

相关文章

如何用python打造新年氛围组?【内含html源码赠送】

嗨害大家好鸭&#xff01;我是小熊猫~ 离正式过新年还有24天~ 这次给大家整一个花活~ 就是每时每秒都提醒离新年还有多久 我这里还有一些中国结的python代码&#xff0c; 大家可以文末名片自行领取鸭~ 倒计时 效果展示 有什么python相关报错解答自己不会的、或者源码资料/…

操作系统——计算机系统概述

操作系统——计算机系统概述一、操作系统的概念1、定义2、功能和目标二 、操作系统特征1、并发性2、共享性3、虚拟性4、异步性三、操作系统的发展1、手工操作阶段2、批处理阶段——单道批处理系统2、批处理阶段——多道批处理系统3、实时操作系统四、运行机制和体系结构1、运行…

EnvironmentAware 接口的使用

文章目录1、EnvironmentAware 接口作用2、实际应用3、代码演示1&#xff09;基本配置准备2&#xff09;增加属性配置文件 application.properties3&#xff09;增加配置类实现 EnvironmentAware 接口4、编写 main 方法的类 SpringTest.java5、运行 main 方法查看结果1、Environ…

Java之线程状态

目录一、简介二、线程状态1、新建状态&#xff08;New&#xff09;2、运行状态&#xff08;Runnable&#xff09;3、阻塞状态&#xff08;Blocked/Waiting/TimeWaiting&#xff09;&#xff08;1&#xff09;被阻塞状态&#xff08;Blocked&#xff09;&#xff08;2&#xff0…

新新新~Revit插件【建模助手】7大模块介绍

年底了&#xff0c;大家是不是都在喝茶、聊天、等跨年&#xff1b;而我&#xff0c;建模助手&#xff0c;则在——总结、规划、PPT。 不盘不知道&#xff0c;一盘可吓了我一大跳&#xff01;没想到咱们在过去一年居然建设了这么多丰功伟绩&#xff1a; 说点标志性的&#xff…

到底什么是电子管(真空管)?

1883年&#xff0c;著名发明家托马斯爱迪生&#xff08;Thomas Edison&#xff09;在一次实验中&#xff0c;观察到一种奇怪现象。当时&#xff0c;他正在进行灯丝&#xff08;碳丝&#xff09;的寿命测试。在灯丝旁边&#xff0c;他放置了一根铜丝&#xff0c;但铜丝并没有接在…

WebRTC系列-视频VideoBroadcaster与视频流

文章目录 1. VideoBroadcaster 添加sink2. VideoBroadcaster 视频数据分发OnFrame方法在前面的文章视频的采集流程中,视频数据采集完成后到编码器之前,如果需要渲染处理那里需要两路视频,WebRTC是实现了一个视频分发器VideoBroadcaster;用来分发采集的视频帧; 这篇文章主要…

Nginx网页配置工具nginxWebUI

今天应该是我们公司 2022 年最后一天上班了&#xff0c;给自己做个简单的年度总结吧&#xff1a; 1月&#xff1a;做核酸 2月&#xff1a;做核酸 3月&#xff1a;做核酸&#xff0c;半个月左右居家办公 4月&#xff1a;做核酸&#xff0c;静态管理 5月&#xff1a;做核酸&…

贪吃蛇基础·框架版(简单)开头有原码

更新不易&#xff0c;麻烦多多点赞&#xff0c;欢迎你的提问&#xff0c;感谢你的转发&#xff0c; 最后的最后&#xff0c;关注我&#xff0c;关注我&#xff0c;关注我&#xff0c;你会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我…

XStream < 1.4.20 栈缓冲区溢出漏洞

漏洞描述 XStream 是一个轻量级的、简单易用的开源Java类库&#xff0c;它主要用于将对象序列化成XML&#xff08;JSON&#xff09;或反序列化为对象。 在1.4.20之前的版本中存在栈缓冲区溢出漏洞&#xff0c;从而导致通过操纵已处理的输入流来造成拒绝服务。 在使用集合和映…

一文掌握FastDeploy Serving服务化部署(打造线上证件照制作系统,含完整代码)

目录一、概述1.1 服务化部署1.2 FastDeploy简介二、搭建线上证件照制作系统2.1 准备环境2.1.1 安装Docker2.1.2 安装NVIDIA Container Toolkit2.1.3 获取FastDeploy Serving镜像2.2 部署模型2.2.1 准备模型仓库2.2.2 编写配置文件config.pbtxt2.2.3 服务启动2.3.4 测试访问2.3 …

盒子模型、CSS 中选择器优先级以及权重计算、px、em 和 rem 的区别

CSS 的盒模型&#xff1f; 盒子模型&#xff08;Box Modle&#xff09;可以用来对元素进行布局&#xff0c;包括内边距&#xff0c;边框&#xff0c;外边距&#xff0c;和实际 内容这几个部分 盒子模型分为两种 第一种是 W3C 标准的盒子模型&#xff08;标准盒模型&#xff09;…

微软 Outlook 如何修改邮件列表中的字体或字号

因为电脑的操作系统是默认英文的&#xff0c;默认的字体实在是太难看了。 解决方案 我们是可以对字体进行修改的。 例如修改成下面的方案。 选择视图 首先&#xff0c;先选定顶部的视图。 然后在当前的视图下&#xff0c;选择视图设置。 随后在弹出的窗口中&#xff0c;选择…

一劳永逸解决latex 80%bug

Latex编辑器也用过很多了&#xff0c;这种问题出现&#xff0c;一般就是}没对应啥的&#xff0c;但是这次检查了半天&#xff0c;就是找不到问题。 最后有一个快捷的解决方案&#xff0c;就是overleafhttps://www.overleaf.com/project/6347bab20a4c120392547ff3 写文章五六年…

美颜sdk是如何美化皮肤的?

当下&#xff0c;互联网娱乐飞速发展&#xff0c;一些社交拍摄的平台逐渐爆火&#xff0c;例如短视频和直播类平台&#xff0c;此类平台基本都需要真人出镜&#xff0c;因此大家或多或少都有些“颜值焦虑”&#xff0c;平台方也正是因为这个原因&#xff0c;开始为大家接入美颜…

Mathorcup数学建模竞赛第五届-【妈妈杯】D题:图像去噪中几类稀疏变换的矩阵表示(附特等奖获奖论文和matlab代码实现)

赛题描述 假设一幅二维灰度图像 X 受到加性噪声的干扰:Y=X+N , Y 为观察到的噪声图像, N 为噪声。通过对于图像 Y 进行稀疏表示可以达到去除噪声的目的。任务: 2. 利用 Cameraman 图像中的一个小图像块(见图 1 )进行验证。 3. 分析稀疏系数矩阵,比较四种方法…

写作历时一个月,长达8000字的年终总结——[2022年终总结]不要怕,请勇敢的向前走

个人博客&#xff1a;武师叔 ❤️ 做一个有趣而不甘平庸的人&#xff01;&#xff01;❤️ 哈喽哈喽&#xff0c;好久不见&#xff0c;我的老朋友最近还好吗 距离上次7.21凌晨1:06完稿的【年中总结】&#xff0c;下半年也匆匆过去啦~ 其实总的来说&#xff0c;下半年过得并不…

医药流通企业如何安全访问医药ERP?无需公网IP和改变现有IT架构

随着目前医药流通行业竞争的加剧&#xff0c;市场供应日趋饱和&#xff0c;传统的粗放式管理缺陷逐渐暴露&#xff0c;导致从事医药行业企业的利润不同程度的下滑&#xff0c;想要满足医药行业客户的个性化需求&#xff0c;为适应企业未来发展&#xff0c;医药流通行业越来越多…

2022 IoTDB Summit:Apache IoTDB PMC 张金瑞《为物联网场景优化的时序数据库共识协议》...

12 月 3 日、4日&#xff0c;2022 Apache IoTDB 物联网生态大会在线上圆满落幕。大会上发布 Apache IoTDB 的分布式 1.0 版本&#xff0c;并分享 Apache IoTDB 实现的数据管理技术与物联网场景实践案例&#xff0c;深入探讨了 Apache IoTDB 与物联网企业如何共建活跃生态&#…

再学C语言19:循环控制语句——关系运算符

关系表达式&#xff08;relational expression&#xff09;&#xff1a;进行比较的判断表达式 关系运算符&#xff08;relational operator&#xff09;&#xff1a;关系表达式中出现的运算符 一、关系运算符 关系运算符运算符含义<小于<小于或等于等于>大于或等于&…