【精通函数式编程】(四)流-Stream API原理解析

简介: 本文通过集合引出Stream流,主要讲解了流的基本概念、使用的原理,Stream流水线的运行原理

image.png

前言:

📫 作者简介:小明 java 问道之路,专注于研究计算机底层,就职于金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的设计和架构📫

🏆 Java 领域优质创作者、阿里云专家博主、华为云享专家🏆

🔥 如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主哦

本文导读

集合是java中使用最多的数据结构,我们如何处理大量元素就是个问题,多线程(线程池)+迭代器性能是还可以,但是太麻烦,也不利于开发和管理,并发的问题还要考虑,这个时候为了保证java的流行性,或者说为了不让java淘汰,在高版本搞出来 流(Stream)。

一、集合与流

集合是java中使用最多的数据结构,包括List、Map,面向对象编程更像是面向集合面向数据结构编程,当我们要处理大量元素的时候,往往会使用多线程,多线程还不能直接创建,要使用线程池创建,麻烦的一批(Java开发规范);

我们如何处理大量元素就是个问题,多线程(线程池)+迭代器性能是还可以,但是太麻烦,也不利于开发和管理,并发的问题还要考虑,这个时候为了保证java的流行性,或者说为了不让java淘汰,在高版本搞出来 流(Stream)。

流(Stream)是声明式处理集合的,我们可以把他当做一个高级的 迭代器+多线程容器,他不能简单理解为一个 流 数据结构,集合(List、Map)可以增删改查,虽然流可以实现诸如过滤、合并,分组等等操作,但是其元素是 按需计算,这其实是一种生产者-消费者模式,流就像一个 正在创建的集合,他会按要求变化后计算值。

下面三段代码,for循环遍历list + if、list转迭代器 + if、等同逻辑

// for + if
        List<BigDecimal> orderAmt = new ArrayList<>();
        for (OrderInfo orderInfo : orderInfos) {
            if (orderInfo.getOrderAmt().compareTo(BigDecimal.ZERO) > 0) {
                orderAmt.add(orderInfo.getOrderAmt());
            }
        }
        // 迭代器 + if
        Iterator<OrderInfo> it = orderInfos.iterator();
        while (it.hasNext()) {
            OrderInfo orderInfo = it.next();
            if (orderInfo.getOrderAmt().compareTo(BigDecimal.ZERO) > 0) {
                orderAmt.add(orderInfo.getOrderAmt());
            }
        }
        // Stream流 + Lambda表达式
        List<BigDecimal> collect = orderInfos.stream()
                .filter(orderInfo -> orderInfo.getOrderAmt().compareTo(BigDecimal.ZERO) > 0)
                .map(orderInfo -> orderInfo.getOrderAmt()).collect(Collectors.toList());

二、什么是流

流是Java 高版本的API,是声明式处理集合的,声明式(简洁)就是说我想要做什么而不是如何实现,把他当做一个高级的 迭代器(可复合-灵活)+多线程容器(可并行-性能好)

要了解流是什么,还要了解他的定义、类路径、如何使用,以及一些使用规范

流在 java.util.stream.Stream 接口中定义,我们可以看到接口注释中有大量讲解,我们就根据这些注释学习流

我们看源码,该类里面有大量方法接口(就和集合一样),可以访问元素,但是集合是数据结构,所以他主要目的是使用特定算法和数据结构存储/访问元素;但流的目的是计算。Stream<T> 流的接收元素是泛型,流会使用一个数据源(这个数据源流不会改变顺序)

image.png

注释中说了,1、流是懒加载原则;仅当 端操作启动时才对源数据执行计算,并且仅在需要时消耗源元素;2、流不会改变原有的数据源;3、流只操作一次;4、可能会抛出 IllegalStateException;5、流不需要被关闭;6、流是内部迭代。详细论证我们放到下面

三、流的使用原则

流使用包括3件事,要有数据源来执行操作,要有一个链这个链试试中间操作的步骤,一个终端操作生成结果。下面代码会论证 这些原则以及上述原理

流只能遍历一次,且是按顺序遍历(流是按顺序遍历的,不会一个List都过滤完,才map,而是每个元素都是流水线执行的)

// 流是按顺序遍历的,不会一个List都过滤完,才map,而是每个元素都是流水线执行的
List<BigDecimal> collect = orderInfos.stream().filter(orderInfo -> {
    System.out.println("===filter" + orderInfo.getOrderAmt());
    return orderInfo.getOrderAmt().compareTo(BigDecimal.ZERO) > 0;
}).map(orderInfo -> {
    System.out.println("===map" + orderInfo.getOrderAmt());
    return orderInfo.getOrderAmt();
}).collect(Collectors.toList());
System.out.println(collect);
// 流只能操作一次,当再次使用的时候会报错IllegalStateException:operated upon or closed
Stream<BigDecimal> stream = collect.stream();
stream.forEach(System.out::print);
System.out.println("===========");
stream.forEach(System.out::print);

image.png

流,java.util.stream.Stream 接口中定义 很多方法,这些方法分为 两大类。filter、map、limit、forEach、peek 等,可以连成流水线;collect 触发流水线执行并关闭(终端操作)。我们把连接起来称为中间操作,关闭流的称为终端操作。一个流的使用原则就是要有数据源、中间操作、终端从操作

四、流的运行原理

为什么Stram流、Lamda表达式式写法又叫作函数式编程?一是调用手法像是函数一般,只须传入参数即可调用;二是Lamda实现方式为生出静态函数调用而成

下图执行步骤标号 1 2 3 4,那流是如何 解决 Stream流水线的?

image.png

Stream中用某种实例化后的PipelineHelper来代表Stage,将具有先后顺序的各个Stage连到一起,就构成了整个流水线。跟Stream相关类和接口的继承关系图示。

image.png

上图中Head用于表示第一个Stage,即调用调用诸如Collection.stream()方法产生的Stage,很显然这个Stage里不包含任何操作;StatelessOp和StatefulOp分别表示无状态和有状态的Stage,对应于无状态和有状态的中间操作。

下图中通过Collection.stream()方法得到Head也就是stg0,紧接着调用一系列的中间操作,不断产生新的Stream。这些Stream对象以双向链表的形式组织在一起,构成整个流水线,由于每个Stage都记录了前一个Stage和本次的操作以及回调函数,依靠这种结构就能建立起对数据源的所有操作。这就是Stream记录操作的方式。

image.png

小结

本文通过集合引出Stream流,主要讲解了流的基本概念、使用的原理,Stream流水线的运行原理。

相关文章
|
18小时前
|
安全 Java API
Java Stream API详解与使用
Java Stream API是Java 8引入的特性,提供函数式操作处理集合,支持链式操作和并行处理,提升代码可读性和性能。关键点包括:延迟执行的中间操作(如filter, map)和触发计算的终端操作(如collect, forEach)。示例展示了如何从Person列表过滤出年龄大于20的姓名并排序。使用Stream时注意避免中间操作的副作用,终端操作后Stream不能复用,以及并行操作的线程安全性。
|
1天前
|
Java 程序员 API
Java 8新特性之Lambda表达式与Stream API的深度解析
【5月更文挑战第12天】本文将深入探讨Java 8中的两个重要新特性:Lambda表达式和Stream API。我们将从基本概念入手,逐步深入到实际应用场景,帮助读者更好地理解和掌握这两个新特性,提高Java编程效率。
16 2
|
3天前
|
Java API
【JAVA进阶篇教学】第三篇:JDK8中Stream API使用
【JAVA进阶篇教学】第三篇:JDK8中Stream API使用
|
3天前
|
XML JavaScript 数据格式
Beautiful Soup 库的工作原理基于解析器和 DOM(文档对象模型)树的概念
Beautiful Soup 使用解析器(如 html.parser, lxml, html5lib)解析HTML/XML文档,构建DOM树。它提供方法查询和操作DOM,如find(), find_all()查找元素,get_text(), get()提取信息。还能修改DOM,添加、修改或删除元素,并通过prettify()输出格式化字符串。它是处理网页数据的利器,尤其在处理不规则结构时。
7 2
|
5天前
|
机器学习/深度学习 人工智能 数据可视化
号称能打败MLP的KAN到底行不行?数学核心原理全面解析
Kolmogorov-Arnold Networks (KANs) 是一种新型神经网络架构,挑战了多层感知器(mlp)的基础,通过在权重而非节点上使用可学习的激活函数(如b样条),提高了准确性和可解释性。KANs利用Kolmogorov-Arnold表示定理,将复杂函数分解为简单函数的组合,简化了神经网络的近似过程。与mlp相比,KAN在参数量较少的情况下能达到类似或更好的性能,并能直观地可视化,增强了模型的可解释性。尽管仍需更多研究验证其优势,KAN为深度学习领域带来了新的思路。
63 5
|
5天前
|
敏捷开发 测试技术 持续交付
极限编程(XP)原理与技巧:深入解析与实践
【5月更文挑战第8天】极限编程(XP)是一种敏捷开发方法,注重快速反馈、迭代开发和简单设计,以提高软件质量和项目灵活性。关键原则包括客户合作、集体代码所有权、持续集成等。实践中,使用故事卡片描述需求,遵循编程约定,实行TDD,持续重构,结对编程,并定期举行迭代会议。通过理解和应用XP,团队能提升效率,应对变化。
|
6天前
|
供应链 搜索推荐 API
API在电子商务中的应用与优势:深入解析
API是电子商务成功的关键,它们不仅促进了技术创新,还提高了用户体验和运营效率。随着技术的不断进步,API将继续在电子商务领域发挥更加重要的作用。电子商务平台通过利用API,可以更加灵活地适应市场变化,提供更加丰富和个性化的购物体验,最终实现业务的增长和扩展。
|
7天前
|
缓存 自然语言处理 JavaScript
万字长文深度解析JDK序列化原理及Fury高度兼容的极致性能实现
Fury是一个基于JIT动态编译的高性能多语言原生序列化框架,支持Java/Python/Golang/C++/JavaScript等语言,提供全自动的对象多语言/跨语言序列化能力,以及相比于别的框架最高20~200倍的性能。
|
12天前
|
分布式计算 Java API
Java 8新特性之Lambda表达式与Stream API
【5月更文挑战第1天】本文将介绍Java 8中的两个重要特性:Lambda表达式和Stream API。Lambda表达式是一种新的函数式编程语法,可以简化代码并提高可读性。Stream API是一种用于处理集合的新工具,可以方便地进行数据操作和转换。通过结合Lambda表达式和Stream API,我们可以更加简洁高效地编写Java代码。
|
13天前
|
JavaScript 网络协议 数据处理
Node.js中的Buffer与Stream:深入解析与使用
【4月更文挑战第30天】本文深入解析了Node.js中的Buffer和Stream。Buffer是处理原始数据的全局对象,适用于TCP流和文件I/O,其大小在V8堆外分配。创建Buffer可通过`alloc`和`from`方法,它提供了读写、切片和转换等操作。Stream是处理流式数据的抽象接口,分为可读、可写、双工和转换四种类型,常用于处理大量数据而无需一次性加载到内存。通过监听事件和调用方法,如读取文件的可读流示例,可以实现高效的数据处理。理解和掌握Buffer及Stream能提升Node.js应用的性能。

推荐镜像

更多