【Java基础教程】(三十)Java新特性篇 · 第十讲: Stream流——释放流式编程的效率与优雅,狂肝万字只为透彻讲清 Stream流!~

news2024/11/27 2:22:42

Java基础教程之新特性 · Stream流

  • 1️⃣ 概念及特征
  • 2️⃣ 优势和缺点
  • 3️⃣ 使用
    • 3.1 语法
    • 3.2 常用API详解
    • 3.3 案例
  • 4️⃣ 应用场景
  • 5️⃣ 使用技巧
  • 6️⃣ 并行流 ParallelStream
  • 🌾 总结

在这里插入图片描述

1️⃣ 概念及特征

Java的Stream流是在Java 8中引入的一种用于处理集合数据的功能强大且易于使用的工具,旨在简化集合框架的操作。它的设计目的是为了提供一种更简洁、更灵活和更可读的方式来处理集合数据。

在之前,我们通常使用迭代器或循环来遍历和操作集合元素,这种方式容易出错且代码冗长。Java 8通过引入Stream流来解决这个问题,提供了一种函数式编程风格的集合操作方法。

Stream流是对集合进行操作的高级抽象,可以将集合看作是一种源(source),而Stream表示这个源上进行的计算操作序列。 通过使用Stream API,我们可以以流水线方式处理数据,并进行各种转换和聚合操作。

在Java中,Stream流分为两种类型:

  • 流(Stream):表示顺序流,按照数据源的顺序进行操作,适用于串行操作。
  • 并行流(ParallelStream):表示并行流,可以同时对数据源的多个元素进行操作,适用于并行计算。

Stream流具有以下特点:

  • 流是一次性的:流不会保存元素,它仅仅描述了操作的序列,并且在执行聚合操作之后就被消耗掉了。即使我们对一个流执行多个操作,每个操作也只会在需要输出结果时才会执行,并且在执行完毕后,流不能再次使用。这与传统的集合不同,集合可以随时进行增删元素的操作;
  • 流是无状态的:流的操作不会修改原始数据结构,而是通过创建一个新的流来执行操作,并最终返回一个结果。原始数据结构保持不变。这种无状态的特性使得流操作可以并行处理数据,不用担心多线程下的数据竞争问题;
  • 流是延迟执行的:流的操作被称为延迟执行,也就是说,在流的聚合操作被触发之前,中间操作不会立即执行。这意味着我们可以先构建一个复杂的流操作链,然后在需要结果的时候才触发最终的操作。这种延迟执行的机制有助于优化性能,避免不必要的计算。

Stream流的实现原理主要基于迭代器和函数式编程的思想。在内部迭代的过程中,流通过一系列操作进行链式处理,将每个元素传递给下一个操作,并最终生成结果。

在并行流的情况下,流将输入数据分成多个小块,分配给不同的线程并行处理。处理完后,再合并结果并返回。

2️⃣ 优势和缺点

Stream流具有以下优点:

  • 简洁:使用流的聚合操作可以极大地减少代码量;
  • 高效:流的并行操作可以利用多核处理器提高运行效率;
  • 函数式编程:流的操作方法遵循函数式编程的思想,使代码更加简洁、易读和可维护;
  • 可复用:可以使用复合操作将多个流操作链在一起。

然而,Stream流也有一些缺点:

  • 可读性降低:对于复杂的操作,使用Stream可能比传统的循环方式可读性稍差;
  • 一次性使用:一旦流被使用过,就不能再次使用,需要重新创建一个新的流;
  • 可能会影响性能:虽然并行流可以提高运行效率,但在某些情况下,额外的分组和合并操作可能会造成性能下降。

3️⃣ 使用

3.1 语法

Stream提供了两种类型的操作:中间操作和终端操作。中间操作用于链式调用,并可以有多个,而终端操作是触发计算的地方。

而使用Stream主要分为三个步骤:

  • 创建流:也即获取一个Stream对象,可以通过集合、数组或者其他方式创建一个Stream。如可以使用Stream.of()方法创建流;
  • 进行中间操作:对Stream进行连续的中间操作,包括过滤、映射、排序、去重等处理。如可以使用forEach()方法遍历流中的元素,并使用filter()map()sorted()等方法对流进行操作;
  • 执行终结操作:最后使用一个终结操作来触发计算并产生结果,如收集、聚合、遍历等。如可以使用reduce()方法进行元素的归约操作,使用collect()方法进行元素的收集操作。

3.2 常用API详解

Stream API提供了丰富的操作方法,可根据不同的需求灵活选择。常用的操作API有:

  • Intermediate操作:如filter()map()sorted(),用于对元素进行筛选、映射、排序等操作。
  • Terminal操作:如forEach()count()collect(),用于对流进行最终的输出、统计和收集操作。
  • Short-circuiting操作:如findFirst()anyMatch()allMatch(),用于在满足条件时立即终止流的操作。

以下是一些Stream操作API详情列表:

类型方法作用
中间操作filter(Predicate)过滤符合条件的元素
map(Function)对每个元素应用转换函数
flatMap(Function)将每个元素转换成Stream对象,然后将所有的Stream连接成一个Stream
distinct()去除重复的元素
sorted([Comparator])排序元素,默认为自然排序
limit(n)截取指定数量的元素
skip(n)跳过指定数量的元素
peek(Consumer)对每个元素执行操作,不影响流中的其他元素
takeWhile(Predicate)从开头开始连续取元素满足指定条件,直到遇到不满足条件的元素
dropWhile(Predicate)从开头开始连续跳过元素满足指定条件,直到遇到不满足条件的元素
终结操作collect(Collector)将流转换为集合或其他数据结构
forEach(Consumer)遍历流中的元素,并对其执行操作
reduce(BinaryOperator)使用给定的二元操作符将元素归约成一个值
max([Comparator])找出流中的最大值
min([Comparator])找出流中的最小值
toArray()将流中的元素转换为数组
count()统计流中的元素数量
findFirst()返回满足条件的第一个元素
findAny()返回任意满足条件的元素
anyMatch(Predicate)判断流中是否存在任意一个元素满足给定条件
allMatch(Predicate)判断流中所有元素是否都满足给定条件
noneMatch(Predicate)判断流中是否没有任何元素满足给定条件

3.3 案例

下面是一个简单的Java程序,演示了上述所有方法的使用:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamOperationsDemo {
    public static void main(String[] args) {
        // 创建一个包含整数的集合
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 1, 2, 3, 4, 5);

        // filter: 过滤掉大于3的元素
        List<Integer> filteredList = numbers.stream()
                .filter(num -> num <= 3)
                .collect(Collectors.toList());
        System.out.println("Filtered List: " + filteredList);

        // map: 将每个元素乘以2
        List<Integer> mappedList = numbers.stream()
                .map(num -> num * 2)
                .collect(Collectors.toList());
        System.out.println("Mapped List: " + mappedList);
        
        // flatMap: 将每个元素转换成Stream对象,然后将所有的Stream连接成一个Stream
        List<String> words = Arrays.asList("Hello", "World");
        List<String> flatMappedList = words.stream()
                .flatMap(word -> Arrays.stream(word.split("")))
                .collect(Collectors.toList());
        System.out.println("FlatMapped List: " + flatMappedList);

        // distinct: 去除重复的元素
        List<Integer> distinctList = numbers.stream()
                .distinct()
                .collect(Collectors.toList());
        System.out.println("Distinct List: " + distinctList);

        // sorted: 对元素进行排序
        List<Integer> sortedList = numbers.stream()
                .sorted()
                .collect(Collectors.toList());
        System.out.println("Sorted List: " + sortedList);

        // limit: 截取指定数量的元素
        List<Integer> limitedList = numbers.stream()
                .limit(3)
                .collect(Collectors.toList());
        System.out.println("Limited List: " + limitedList);

        // skip: 跳过指定数量的元素
        List<Integer> skippedList = numbers.stream()
                .skip(3)
                .collect(Collectors.toList());
        System.out.println("Skipped List: " + skippedList);

        // peek: 对每个元素执行操作,不影响流中的其他元素
        List<Integer> peekedList = numbers.stream()
                .peek(num -> System.out.println("Peeking element: " + num))
                .collect(Collectors.toList());

        // takeWhile: 从开头开始连续取元素满足条件,直到遇到不满足条件的元素
        List<Integer> takenList = numbers.stream()
                .takeWhile(num -> num < 4)
                .collect(Collectors.toList());
        System.out.println("Taken List: " + takenList);

        // dropWhile: 从开头开始连续跳过元素满足条件,直到遇到不满足条件的元素
        List<Integer> droppedList = numbers.stream()
                .dropWhile(num -> num < 4)
                .collect(Collectors.toList());
        System.out.println("Dropped List: " + droppedList);

        // collect: 将流转换为集合或其他数据结构
        List<Integer> collectedList = numbers.stream()
                .collect(Collectors.toList());
        System.out.println("Collected List: " + collectedList);

        // forEach: 遍历流中的元素,并对其执行操作
        numbers.stream()
                .forEach(System.out::println);

        // reduce: 使用给定的二元操作符将元素归约成一个值
        int sum = numbers.stream()
                .reduce(0, Integer::sum);
        System.out.println("Sum: " + sum);

        // max: 找出流中的最大值
        int max = numbers.stream()
                .max(Integer::compare)
                .orElse(-1);
        System.out.println("Max: " + max);

        // min: 找出流中的最小值
        int min = numbers.stream()
                .min(Integer::compare)
                .orElse(-1);
        System.out.println("Min: " + min);

        // toArray: 将流中的元素转换为数组
        Integer[] array = numbers.stream()
                .toArray(Integer[]::new);
        System.out.println("Array: " + Arrays.toString(array));

        // count: 统计流中的元素数量
        long count = numbers.stream()
                .count();
        System.out.println("Count: " + count);

        // findFirst: 返回满足条件的第一个元素
        int first = numbers.stream()
                .findFirst()
                .orElse(-1);
        System.out.println("First: " + first);

        // findAny: 返回任意满足条件的元素
        int any = numbers.stream()
                .findAny()
                .orElse(-1);
        System.out.println("Any: " + any);

        // anyMatch: 判断流中是否存在任意一个元素满足给定条件
        boolean anyMatch = numbers.stream()
                .anyMatch(num -> num % 2 == 0);
        System.out.println("Any Match: " + anyMatch);

        // allMatch: 判断流中所有元素是否都满足给定条件
        boolean allMatch = numbers.stream()
                .allMatch(num -> num % 2 == 0);
        System.out.println("All Match: " + allMatch);

        // noneMatch: 判断流中是否没有任何元素满足给定条件
        boolean noneMatch = numbers.stream()
                .noneMatch(num -> num > 10);
        System.out.println("None Match: " + noneMatch);
    }
}

这个程序演示了如何使用Stream的中间操作和终端操作。

程序的运行结果如下:

Filtered List: [1, 2, 3, 1, 2, 3]
Mapped List: [2, 4, 6, 8, 10, 2, 4, 6, 8, 10]
FlatMapped List: [H, e, l, l, o, W, o, r, l, d]
Distinct List: [1, 2, 3, 4, 5]
Sorted List: [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
Limited List: [1, 2, 3]
Skipped List: [4, 5, 1, 2, 3, 4, 5]
Peeking element: 1
Peeking element: 2
Peeking element: 3
Peeking element: 4
Peeking element: 5
Peeking element: 1
Peeking element: 2
Peeking element: 3
Peeking element: 4
Peeking element: 5
Taken List: [1, 2, 3]
Dropped List: [4, 5, 1, 2, 3, 4, 5]
Collected List: [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
1
2
3
4
5
1
2
3
4
5
Sum: 30
Max: 5
Min: 1
Array: [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
Count: 10
First: 1
Any: 1
Any Match: true
All Match: false
None Match: true

4️⃣ 应用场景

Stream流广泛应用于数据处理、集合操作、并行计算等场景。它可以使代码更简洁、易读和具有可维护性,同时充分发挥多核处理器的计算能力。如下:

  • 数据分析:根据条件过滤出需要的数据,并进行统计、汇总或生成报表;
  • 数据处理:对大规模、复杂的数据集合进行筛选、转换、排序以及聚合和分组等;
  • 数据查询:通过多个中间操作构建复杂的查询条件,获取符合要求的数据;
  • 并行处理:在多核处理器上可利用parallel方法实现并行处理大批量数据,提高系统的处理性能。

例如,对于一个电商平台的订单数据,我们可以使用流来实现以下功能:

  • 筛选出所有金额大于1000的订单;
  • 将订单按照金额从高到低进行排序;
  • 获取前5个订单的信息;


5️⃣ 使用技巧

要使用流,首先需要从数据源创建一个流,然后通过一系列的中间操作和终端操作来对流进行处理和操作。

在使用流时,可以注意以下几点优化技巧:

  • 合理选择流的类型,根据实际情况选择顺序流或并行流,对于大数据集合,考虑使用并行流以提高性能;
  • 尽量减少中间操作的数量,合并多个操作可以减少迭代次数;
  • 尽量避免使用短路操作,以充分发挥并行流的优势;
  • 使用延迟执行的特性,只在需要获取结果时触发终端操作。

6️⃣ 并行流 ParallelStream

并行流(ParallelStream)允许在多线程环境下并发地执行操作,从而提高处理大数据集的效率。

ParallelStream类在Java中没有特有的方法。它与普通的Stream类具有相同的操作方法,可以使用 filtermapflatMapdistinctsortedlimitskippeek 等方法。这些方法可以在并行流上执行,并发地处理数据。并行流会自动将数据分成多个部分,并在多个线程上同时进行处理,加快了处理速度。

需要注意的是,在使用并行流时,应该要注意线程安全和性能问题。如果并行执行的操作具有共享状态、副作用或依赖于元素之间的顺序,那么可能会导致不正确的结果。并行流适用于对大量元素进行计算密集型操作,但并不适用于有状态或依赖前后元素的操作。因此,在使用并行流时,需要确保操作的可靠性,并在必要时使用同步措施来保证线程安全。

除了以上普通的Stream操作方法,在并行流中还可以使用.parallel().sequential()方法切换并行流和顺序流的操作模式。.parallel()方法将流转换为并行流,允许并发地对元素进行操作。而.sequential()方法则将并行流转回为顺序流,仅使用单线程顺序地处理元素。

🌾 总结

Java Stream流为我们提供了一种简洁而强大的方式来操作数据集合。它具有许多优点,如简化操作、惰性求值和并行处理。同时也有一些缺点,如学习成本稍高和可读性稍差。然而,在正确使用和优化Stream的情况下,可以极大地提高代码的可读性和维护性,并实现更高效的数据处理与计算。

通过使用流,我们可以以更直观、简洁的方式对数据进行处理和操作,并发挥多核处理器的计算能力。

然而,使用流也需要注意数据量、性能和适用场景等因素。最重要的是根据具体情况选择合适的流类型,并根据实际需求合理组合流的操作,以实现更高效、可读性更好的代码。


温习回顾上一篇(点击跳转)《【Java基础教程】(二十九)Java新特性篇 · 第九讲:函数式接口——概念及优缺点、语法形式及实战技巧、 内置函数式接口与应用场景~》

继续阅读下一篇(点击跳转)《【Java基础教程】(三十一)常用类库篇 · 第一讲:Optional类——解锁Java的Optional操作,消灭那些隐匿的空指针,还程序世界一个安稳!~》

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

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

相关文章

【Linux篇】Linux下的第一个小程序--进度条 蹦迪炫彩进图条

&#x1f49b;不要有太大压力&#x1f9e1; &#x1f49b;生活不是选择而是热爱&#x1f9e1; 文章目录 Linux下第一个小程序&#xff1a;进度条两个背景知识缓冲区回车和换行是一个概念&#xff1f; 进图条原理进度条代码效果其他玩法&#xff1a;蹦迪版进度条 Linux下第一个小…

SpringBoot集成flink

Flink是一个批处理和流处理结合的统一计算框架&#xff0c;其核心是一个提供了数据分发以及并行化计算的流数据处理引擎。 最大亮点是流处理&#xff0c;最适合的应用场景是低时延的数据处理。 场景&#xff1a;高并发pipeline处理数据&#xff0c;时延毫秒级&#xff0c;且兼具…

永别了,垃圾病毒山寨软件!

在下载软件这一块&#xff0c;电脑与手机最大的不同就是手机有专门的应用商店&#xff0c;而电脑我们却只能使用搜索引擎找寻软件官网下载&#xff0c;这对于大多数对电脑不熟悉的人来说&#xff0c;很可能会下载到恶意病毒软件。 尽管从Win10开始&#xff0c;微软已经在系统中…

【kubernetes】关于k8s集群的存储卷

目录 一、存储卷的分类 二、empty存储卷以及特点 三、hostpath存储卷以及特点 四、nfs存储卷以及特点 五、pvc存储卷 查看pv的定义 查看pvc的定义 实操&#xff1a;静态创建pv的方式 实现pvc存储卷 步骤一&#xff1a;先完成nfs的目录共享&#xff0c;需要准备不同的目…

原油数据处理:1.聚类、盐含量测定与近红外光谱快速评估

一、原油种类的聚类分析 在塔里木盆地塔河油田的原油处理过程中&#xff0c;需要对原油进行地球化学特征研究&#xff0c;以了解其成因和特征。根据地球化学手段的综合研究结果&#xff0c;塔河油田奥陶系原油属于海相沉积环境&#xff0c;成熟度较高&#xff0c;正构烷烃分布…

Python爬虫实战第三例【三】【上】

零.实现目标 爬取视频网站视频 视频网站你们随意&#xff0c;在这里我选择飞某速&#xff08;狗头保命&#xff09;。 例如&#xff0c;作者上半年看过的“铃芽之旅”&#xff0c;突然想看了&#xff0c;但是在正版网站看要VIP&#xff0c;在盗版网站看又太卡了&#xff0c;…

爬虫实战——scrapy框架爬取多张图片

scrapy框架的基本使用&#xff0c;请参考我的另一篇文章&#xff1a;scrapy框架的基本使用 起始爬取的网页如下&#xff1a; 点击每张图片&#xff0c;可以进入图片的详情页&#xff0c;如下&#xff1a; 代码实现&#xff1a; 项目文件结构如下 img_download.py文件代码 im…

探究java反射取值与方法取值性能对比

探究java反射取值与方法取值性能对比 由于我开发框架时&#xff0c;经常需要对象取值。常用的取值方式有&#xff1a; 反射取值方法调用取值 环境 同一台电脑&#xff1a; jdk 21.0.2 idea 2023.3.3 1. 测试代码&#xff08;常用&#xff09; 1.1 反射取值 public stat…

IO流操作大集合

1、分类 File只能对文件进行处理&#xff0c;如果想要处理文件里的内容&#xff0c;则要使用文件流。 文件流可分为字符和字节流&#xff1a; 字节流&#xff08;可读写任何格式的文件&#xff09; inputStream&#xff08;抽象父类&#xff09; FileInputStream outputStre…

STM32CubeIDE基础学习-基础外设初始化配置

STM32CubeIDE基础学习-基础外设初始化配置步骤 前言 前面的文章介绍了基础工程的创建步骤&#xff0c;这篇文章就接着在基础工程的基础上来配置相关外设了&#xff0c;下面以STM32F103C8T6的主芯片为例进行简单配置。 基础工程创建步骤回顾 具体的配置步骤流程如下&#xff1…

[两个栈实现队列]

[两个栈实现队列] 一、题目二、思路三、代码 一、题目 二、思路 //思路:两个栈实现队列&#xff0c;栈是先入后出&#xff0c;队列是队尾入&#xff0c;对头出&#xff0c;&#xff08;先入先出&#xff09;&#xff0c;那么可以这样干&#xff0c;假设一个栈Pushst&#xff0c…

Java基础 - 8 - 算法、正则表达式

一. 算法 什么是算法&#xff1f; 解决某个实际问题的过程和方法 学习算法的技巧&#xff1f; 先搞清楚算法的流程&#xff0c;再直接去推敲如何写算法 1.1 排序算法 1.1.1 冒泡排序 每次从数组中找出最大值放在数组的后面去 public class demo {public static void main(S…

如何使用 ArcGIS Pro 统计四川省各市道路长度

在某些时候&#xff0c;我们需要进行分区统计&#xff0c;如果挨个裁剪数据再统计&#xff0c;不仅步骤繁琐、耗时&#xff0c;还会产生一些多余的数据&#xff0c;这里教大家如何在不裁剪数据的情况下统计四川各市的道路长度&#xff0c;希望能对你有所帮助。 数据来源 教程…

web前端之uniApp实现选择时间功能

MENU 1、孙子组件1.1、html部分1.2、JavaScript部分1.3、css部分 2、子组件2.1、html部分2.2、JavaScript部分2.3、css部分 3、父组件3.1、html部分3.2、JavaScript部分 4、效果图 1、孙子组件 1.1、html部分 <template><view><checkbox-group change"ch…

HashSet在添加元素时,是如何判断元素重复的? (原理详解 | 易错案例)

前言&#xff1a;我们知道Set中所存储的元素是不重复的&#xff0c;那么Set接口的实现类HashSet在添加元素时是怎么避免重复的呢&#xff1f; HashSet在添加元素时&#xff0c;是如何判断元素重复的? ● 在底层会先调用hashCode()&#xff0c;注意&#xff0c;Obje…

分享一个完全免费的GPT4站点,gpts也可以用

给大家分享一个完全免费的GPT4站点&#xff0c;gpts也可以用点击链接可用

板级PDN(电源分配网络)设计要点综述

目录 目标阻抗去耦方法 确定目标阻抗 确定目标频点 VRM 去耦电容 安装电感 平面电容 总结 去耦电容 PCB叠层设计 扩展阅读 目标阻抗去耦方法 确定PCB去耦方案的策略是使用频域目标阻抗法&#xff0c;通过层间电容和分立电容器组合的使用&#xff0c;保证电源轨阻抗在…

【MySQL】视图 -- 详解

视图 是一个虚拟表&#xff0c;其内容由查询定义。同真实的表一样&#xff0c;视图包含一系列带有名称的列和行数据。视图的数据变化会影响到基表&#xff0c;基表的数据变化也会影响到视图。 一、基本使用 1、创建视图 create view 视图名 as select 语句; 好处&#xff1a;…

基于springboot+vue实现电子商务平台管理系统项目【项目源码+论文说明】

基于springboot实现电子商务平台管理系统演示 研究的目的和意义 据我国IT行业发布的报告表明&#xff0c;近年来&#xff0c;我国互联网发展呈快速增长趋势&#xff0c;网民的数量已达8700万&#xff0c;逼近世界第一&#xff0c;并且随着宽带的实施及降价&#xff0c;每天约有…

Node.js最准确历史版本下载(以下载Node.js16.17.1版本为例)

先进入官网:Node.js https://nodejs.org/en 括号中LTS代表稳定版本. 嫌其他冗余博客帖子多&#xff0c;找起来费眼睛,可以到/release下载:Node.js,在blog后面加/release https://nodejs.org/en/blog/release/ 点击next翻页,跟上面同样的步骤