JavaSE从零开始到精通(七) - Stream流

news2024/9/22 7:32:50

1. 概述

Java 8引入了Stream API,它提供了一种高效且易于使用的处理集合数据的方式。Stream流可以被认为是一种高级的迭代器,允许我们在集合上进行复杂的操作,例如过滤、映射、排序、归约等,而这些操作可以链式调用,形成流水线。

Stream流采用惰性求值的策略,只有当终端操作被调用时才会执行中间操作,这种特性可以提高性能,避免不必要的计算。

  1. 流的来源

    • 流可以从集合(如List、Set、Map等)、数组、I/O资源等数据源创建。
    • 返回值:Stream
  2. 中间操作

    • 中间操作是流的一部分,允许对流进行转换。它们始终返回一个新的流。例如:过滤、映射、排序、去重等。
    • 返回值:Stream
  3. 终端操作

    • 终端操作是流的最后一步。执行终端操作会触发流的遍历并完成流的处理。终端操作可能会产生一个值或者一个副作用(例如I/O操作)。例如:收集到集合、聚合操作(求和、平均值)、遍历打印等。
    • 返回值:非Stream,一般为基本数据类型。

注意Stream流和管道操作不同: 

  • 在操作系统中,管道通常用于进程间通信。管道允许一个进程的输出直接作为另一个进程的输入,从而实现进程间的数据传递。例如 command1 | command2,这将把 command1 的输出作为 command2 的输入。 
  • Stream流执行流程:遇到终结操作,就会流水线一样,通过内部迭代,一个一个元素的过滤,不是管道,前者的输出作为后者的输入。 

例如: 

管道的话:第一次把姓张和姓李的获取出来(张三三,张三,李四四,李四),然后作为输出结果给下一个中间操作。

Stream流:触发终结操作sum(),会执行中间操作,取出张三走流水线,如果符合1,进入2,依次到被其他中间操作筛去,或者被终结操作消费,通过内部迭代依次在处理流中其他数据。

2. 流的特点

  • 不可变性:Stream本身并不存储数据,数据的实际存储可以是集合、数组、文件等,而Stream则提供了便捷和功能强大的API来处理这些数据。它只是提供一种视图或者操作集合数据的流程。

对于每次中间操作,都是返回一个新的流对象,这和字符串非常相似。

  • 延迟执行:流的中间操作可以延迟执行,只有在执行终端操作时才会实际处理数据。

这种特性可以提高性能,避免不必要的计算。

  • 内部迭代:与传统的集合迭代器相比,流使用内部迭代(通过函数式编程方式),开发者不需要显式地管理迭代器或循环。

3. Stream流的获取

1.单列集合的Stream流对象获取

List<Integer> list = new ArrayList<>();
Collections.addAll(list,2,3,5,7);
Stream<Integer> stream = list.stream();

2.双列集合的Stream流对象获取

        //创建双列集合对象
        Map<String,String> hash = new HashMap<>();
        //Stream流对象获取: 把双列集合转换成单列集合(根据双列集合获取其键的集合或者是键值对的结合)获取
        Stream<String> s2 = hash.keySet().stream();
        System.out.println(s2);
        Stream<Map.Entry<String, String>> s3 = hash.entrySet().stream();
        System.out.println(s3);

3.数组的Stream流对象获取

String[] strs = {"张三三","李四四","王五五"};
Stream<String> s4 = Arrays.stream(strs);

4.散装数据的Stream流对象获取

        //借助于Stream接口的静态方法of,获取散装数据对应的流对象
        Stream<String> s5 = Stream.of("张三三", "李四四", "王五五");

 4. Stream流常用方法

 4.1 中间方法

中间操作的设计初衷是为了定义对数据流的处理流程,如过滤、映射等,同时保留流的操作链,以便后续操作。

终端操作则负责触发流的实际处理,并生成最终结果或引发副作用,如收集结果或打印输出。

这种分工使得代码结构更加清晰,能够有效地管理和复用流的处理逻辑,同时确保了操作的延迟执行和链式调用的灵活性。

1. filter(Predicate)

  • 根据 Predicate 过滤流中的元素,返回符合条件的元素组成的新 Stream。

 

Predicate 接口是 Java 中的一个函数式接口(看见函数式接口,不要忘记考虑使用lambda表达式),它定义了一个抽象方法 test,接受一个参数并返回一个 boolean 值。Predicate 主要用于进行条件判断,常见于集合操作和函数式编程中。

boolean test(T t):对给定的输入值 t 进行判断,返回一个 boolean 值。对于Stream流中的filter是返回的为true,表示通过该流进行下一步操作,返回false舍弃元素。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
                                   .filter(n -> n % 2 == 0)
                                   .collect(Collectors.toList());

保留偶数到新的集合中,例如: 触发collect终结方法后,1 进入流程,1 % 2 == 1返回false,会被舍弃,2 进入流程 2 % 2 == 0 会返回 true,会被保留,进入collect进行累加到list中。 

2. map(Function)

  • 将流中的每个元素映射为另一个元素,通过传入的函数规则进行转换,返回转换后的新 Stream。 

 

Function 是 Java 中的一个函数式接口,定义了一个函数的形式,该函数接受一个参数并产生一个结果。它有一个抽象方法 R apply(T t),接受类型为 T 的参数,返回类型为 R 的结果。 

例如:将字符串转换为大写形式。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
        
        // 使用 map 方法将每个字符串转换为大写形式
        List<String> upperCaseNames = names.stream()
                                          .map(String::toUpperCase)
                                          .collect(Collectors.toList());
        
        System.out.println(upperCaseNames);  // 输出 [ALICE, BOB, CHARLIE]
  • 我们定义了一个 Function<String, String> 类型的 toUpperCaseFunction,它使用方法引用 String::toUpperCase
  • upperCaseNames:接受一个字符串作为输入,然后调用这个字符串的 toUpperCase() 方法来生成一个全大写的新字符串。
  • 我们通过 upperCaseNames.apply(input) 将字符串 "Alice" 转换为全大写形式,并将结果进入下一步流,然后终结操作会将其添加到list集合中,依次处理"Bob" 和 "Charlie"。

3. sorted()

  • 对流中的元素进行排序,默认为自然顺序,除非传入自定义的 Comparator。

例如:对流中字符串按照非自然顺序(字符串长度)进行排序。

    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "pear", "grape", "orange");

        // 自定义一个Comparator,按照字符串长度逆序排序
        Comparator<String> lengthComparator = (str1, str2) -> Integer.compare(str2.length(), str1.length());

        // 使用sorted方法进行排序
        List<String> sortedByLength = words.stream()
                .sorted(lengthComparator)
                .collect(Collectors.toList());

        // 输出排序结果
        System.out.println("Sorted by length (descending): " + sortedByLength);
    }

4. distinct()

  • 去除流中重复的元素,返回由不同元素组成的新 Stream。

例如:对该集合去重,会返回 1 2 3 4 5

List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 4, 5);
List<Integer> distinctNumbers = numbers.stream()
                                       .distinct()
                                       .collect(Collectors.toList());

5. limit(long) / skip(long)

  • limit 用于截取流中的前 n 个元素。
  • skip 则用于跳过流中的前 n 个元素。

 例如:1.只要前三个元素

            2.舍弃前两个元素

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> limitedNumbers = numbers.stream()
                                      .limit(3)
                                      .collect(Collectors.toList());

List<Integer> skippedNumbers = numbers.stream()
                                      .skip(2)
                                      .collect(Collectors.toList());

4.2 终结方法 

终结方法是对 Stream 执行最终操作并产生最终结果的方法。当调用终结方法时,Stream 才会开始实际的处理过程,同时会关闭 Stream,因此一个 Stream 实例只能调用一次终结方法。终结方法通常会返回一个非 Stream 的结果,例如集合、整数等。

1. forEach(Consumer)

  • 对 Stream 中的每个元素执行 Consumer 指定的操作。

 

Consumer 是 Java 中的一个函数式接口,用于表示接受单个输入参数并且不返回任何结果的操作。它定义了一个名为 accept 的抽象方法,该方法接受一个参数,但没有返回值。Consumer 接口通常用于需要执行某些操作而不需要返回值的场景。

  • Consumer:提供数据的消费规则的接口
  • accept(数据):accept方法的方法体就是该数据的消费逻辑 

 这就可以引出:生产者消费者模型

  1. 生产者负责生成数据或者放置任务到共享区域(缓冲区)中,以便消费者进程可以访问。 
  2. 消费者从共享区域获取数据或任务,并进行相应的处理或消费。 
  3. 共享缓存区:生产者和消费者之间共享的数据存储区域。它可以是一个队列、栈或者其他形式的缓冲区。
  4. 生产者将数据放入缓冲区,消费者从缓冲区取出数据。

例如:在函数式编程中,有时我们希望执行某些副作用操作,比如更新状态或打印日志。虽然函数式编程强调无副作用,但有时候还是需要执行这类操作。 

import java.util.stream.Stream;

public class SideEffectExample {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("apple", "banana", "orange", "pear");

        stream.forEach(item -> {
            System.out.println("Processing item: " + item);
            // 执行一些副作用操作
        });
    }
}

2. collect(Collectors)

  • 将 Stream 转换为其他形式,接收一个 Collectors 参数,将 Stream 中的元素累积(底层调用不同集合的添加api,例如add ) 到一个可变结果容器中。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Set<String> nameSet = names.stream()
                          .collect(Collectors.toSet());

3. reduce(BinaryOperator)

  • 使用给定的函数来组合流中的元素,得到单个值。将流中的元素按照给定的函数来组合,最终得到一个单一的结果。这个函数必须是一个 BinaryOperator,即它接受两个相同类型的参数,并返回一个同类型的结果。

BiFunction 接口是Java中的一个函数式接口,它代表了一个接受两个参数并产生一个结果的函数。它的作用主要是定义了一个可以接受两个参数并返回一个结果的函数的形式,从而可以方便地在函数式编程中使用。

例如:对流中的数据求乘积。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> sum = numbers.stream()
                               .reduce((a, b) -> a * b);

4. count()

  • 返回 Stream 中的元素数量。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
long count = names.stream()
                  .count();

5. 静态方法

1. 创建流

Stream.of(T... values)

创建一个包含指定元素的流。

Stream<String> stream = Stream.of("apple", "banana", "orange");

2. 转换流

Stream.concat(Stream<? extends T> a, Stream<? extends T> b)

连接两个流。

Stream<String> stream1 = Stream.of("apple", "banana");
Stream<String> stream2 = Stream.of("orange", "grape");
Stream<String> concatenatedStream = Stream.concat(stream1, stream2);

3. 生成特定类型的流

IntStream.range(int startInclusive, int endExclusive)

生成一个从 startInclusive 到 endExclusive 的整数流(不包括 endExclusive)。

IntStream intStream = IntStream.range(1, 5); // 1, 2, 3, 4

扩展:为什么要使用函数式接口?为什么不直接传参?

传统的面向对象编程中,我们通常是通过定义方法来传递参数和实现功能。然而,函数式编程的核心思想是将函数作为数据进行处理,这种方式更为灵活和简洁。直接传参的方式虽然依然有效,但在需要复杂的行为组合、条件处理或者并行处理时,使用函数式接口和Lambda表达式能够提供更为清晰和简洁的解决方案。 

 

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

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

相关文章

C# 开发监控方法执行耗时

MethodTimer.Fody 是一个功能强大的库,可以用于测量 .NET 应用程序中的方法的执行时间。允许你在不修改代码的情况下,自动地测量和记录方法的执行时间。 这个工具是基于.NET的 weaving 技术,通过修改IL(Intermediate Language,中间语言)代码来插入计时逻辑,从而在方法调…

Python内存管理:引用计数与垃圾回收

✨ 内容&#xff1a; 在Python中&#xff0c;内存管理是一个重要且常常被忽视的话题。了解Python如何管理内存&#xff0c;不仅能帮助我们编写高效的代码&#xff0c;还能避免潜在的内存泄漏问题。今天&#xff0c;我们将通过一个实际案例&#xff0c;深入探讨Python的内存管理…

MIT6.824(6.5840) Lab1笔记+源码

文章目录 其他人的内容&#xff0c;笔记写的更好&#xff0c;思路可以去看他们的MapReduceworkermapreduce coordinatorrpc纠错 源码worker.gocoordinator.gorpc.go 原本有可借鉴的部分 mrsequential.go&#xff0c;多看几遍源码 其他人的内容&#xff0c;笔记写的更好&#xf…

如何实现ORACLE19c 安装包DIY

最近一直忙&#xff0c;本想每周有更新的&#xff0c;但老板一句话&#xff0c;就得去干活&#xff0c;想实现这个愿望&#xff0c;看来真的很难&#xff0c;做一天好人容易&#xff0c;要一辈子做好人&#xff0c;难。所以&#xff0c;看到德哥&#xff0c;尹总监&#xff0c;…

言语理解与表达

** 言语理解与表达 1.逻辑填空 2.语句表达 3.阅读理解

MTK 安卓14 launcher3修改桌面模式,替换某些应用图标,以及定制化Hotseat

原生的launcher的Hotseat如下图(1)所示,我想把效果改成图(2) 图(1) 图(2) 一:定制化HotSeat 修改的类&#xff1a;packages/apps/Launcher3/com/android/launcher3/Hotseat.java &#xff08;1&#xff09;.修改hotseat的宽 Hotseat------->setInsetsOverridepublic void…

Java 22 中的4个永久特性

功能处于孵化或预览阶段是什么意思&#xff1f; 实际上&#xff0c;这是向 Java 编程语言添加新功能的新过程&#xff0c;Java 社区使用这种过程来在 API 和工具处于早期实验阶段时从社区获得反馈&#xff08;孵化功能&#xff09;或已经完全指定但尚未永久的阶段&#xff08;…

ConstraintLayout属性说明

ayout_constraintTop_toTopOf&#xff1a;将某一控件的顶部与另一控件的顶部对齐。 layout_constraintTop_toBottomOf&#xff1a;将某一控件的顶部与另一控件的底部对齐。 layout_constraintBottom_toTopOf&#xff1a;将某一控件的底部与另一控件的顶部对齐。 layout_cons…

3GPP R18 Multi-USIM是怎么回事?(四)

前几篇主要是MUSIM feature NAS 部分内容的总结,这篇开始看RRC部分相关的内容,由于RRC部分内容过长,也分成了2篇。这篇就着重看下musim gap以及RRC触发UE离开RRC Connected mode相关的内容,直入正题, 上面的内容在overview中有提到,对应的是如下38.300中的描述。 处于网络…

【Node.js基础02】fs、path模块

目录 一&#xff1a;fs模块-读写文件 1 加载fs模块对象 2 读制定文件内容文件 3 向文件中写入内容 二&#xff1a;path模块-路径处理 1 问题引入 2 __dirname内置变量 使用方法 一&#xff1a;fs模块-读写文件 fs模块封装了与本机文件系统交互方法和属性 1 加载fs模块…

Win11 改造

记录一些安装 win11 系统之后&#xff0c;对使用不习惯的地方&#xff0c;进行的个人改造 右键菜单 Hiyoung006/Win11Useable: 将Win11右键菜单及资源管理器恢复为Win10样式的脚本 切换到旧版右键菜单&#xff1a; reg add "HKCU\Software\Classes\CLSID\{86ca1aa0-34…

Chapter18 基于物理的渲染——Shader入门精要学习

Chapter18 基于物理的渲染 一、PBS理论和数学基础1.光是什么微表面模型 2.渲染方程3.精确光源4.双向反射分布函数 BRDF5.漫反射项&#xff08;Lambert 模型&#xff09;Lambertian BRDF为&#xff1a;Disney BRDF中漫反射项 6.高光反射项微面元理论BRDF的高光反射项①菲涅尔反射…

LabVIEW和IQ测试仪进行WiFi测试

介绍一个使用LabVIEW和LitePoint IQxel-MW IQ测试仪进行WiFi测试的系统。包括具体的硬件型号、如何实现通讯、开发中需要注意的事项以及实现的功能。 使用的硬件​ IQ测试仪型号: LitePoint IQxel-MW 电脑: 配置高效的台式机或笔记本电脑 路由器: 支持802.11ax (Wi-Fi 6) 的…

便携气象站:科技助力气象观测

在科技飞速发展的今天&#xff0c;便携气象站以其轻便、高效、全面的特点&#xff0c;正逐渐改变着气象观测的传统模式。这款小巧而强大的设备&#xff0c;不仅为气象学研究和气象灾害预警提供了有力支持&#xff0c;更为户外活动、农业生产等领域带来了诸多便利。 便携气象站是…

遗传算法模型Python代码——用Python实现遗传算法案例

一、遗传算法概述 1.1适用范围 遗传算法&#xff08;Genetic Algorithm, GA&#xff09;是一种启发式搜索算法&#xff0c;广泛应用于以下领域&#xff1a; 优化问题&#xff1a;如函数优化、路径规划、资源分配等。机器学习&#xff1a;用于特征选择、超参数优化等。经济与…

服务器系统盘存储不够,添加数据盘并挂载(阿里云)

目录 1.获取数据盘设备名称 2.为数据盘创建分区 3.为分区创建文件系统 4.配置开机自动挂载分区 阿里云数据盘挂载说明链接&#xff1a;在Linux系统中初始化小于等于2 TiB的数据盘_云服务器 ECS(ECS)-阿里云帮助中心 1.获取数据盘设备名称 sudo fdisk -lu 运行结果如下所示…

解决 elementUI 组件在 WebStorm 中显示为未知标签的问题

解决 elementUI 组件在 WebStorm 中显示为未知标签的问题 一、问题 自从转到 ts 之后&#xff0c;编辑器就一直提示用到的 elementUI 标签未知&#xff0c;一直显示一溜黄色警示&#xff0c;很烦&#xff1a; 二、解决 把它改成大写就可以了。 如下&#xff1a; 把整个项目…

【C++】学习笔记——哈希_2

文章目录 十八、哈希3. 实现哈希表哈希表的存储节点哈希函数哈希表的定义哈希表的插入哈希表的查找哈希表的删除测试函数完整代码结果 未完待续 十八、哈希 3. 实现哈希表 哈希表的实现方法有蛮多种&#xff0c;这里我们选一个比较经典的开散列法来实现哈希表。由于STL库里的…

使用PicGo操作gitee图床(及web端html不能访问图片的解决办法)

1.新建仓库 2.输入仓库名称,也就是图床名称,必须设置开源可见 也可以在创建仓库后,点击管理->基本信息->是否开源进行设置 鼠标悬浮到右上角头像->设置 点击私人令牌 点击生成新令牌,填写描述,直接点提交即可 点击提交后输入登录密码会生成一个token秘钥,如下,这个…

新版本异次元荔枝V4自动发卡系统源码

新版本异次元荔枝V4自动发卡系统源码&#xff0c;增加主站货源系统&#xff0c;支持分站自定义支付接口&#xff0c;目前插件大部分免费&#xff0c;UI页面全面更新&#xff0c;分站可支持对接其他分站产品&#xff0c;分站客服可自定义&#xff0c;支持限定优惠。 源码下载&a…