Java Stream介绍和实战

目录

1. 引言

2. Stream 的基本特性

3. 创建 Stream

4. Stream 的中间操作

5. Stream 的终端操作

6. Stream 的性能优化

7. 实例演示

8. 注意事项

9. 结语


1. 引言

Java 中的 Stream 是 Java 8 引入的一种用于对集合进行操作的工具,为开发者提供了一种更便捷、更流畅的方式来处理集合数据。Stream 可以让我们以声明式的方式对集合进行各种操作,如筛选、映射、过滤、排序等,而无需显式地使用循环和临时变量。下面就带大家一起看看Java Stream的使用吧。

2. Stream 的基本特性

Stream 是 Java 8 引入的一种用于对集合进行函数式操作的工具。提供了丰富的 API,支持丰富的操作,如筛选、映射、过滤、排序等。

基本概念

  • 数据源:Stream 可以来自不同类型的数据源,如集合、数组、I/O 等。
  • 流水线:Stream 可以进行一系列的操作,形成一个流水线,但这些操作并不会立即执行,而是在遇到终端操作时才会被触发执行。
  • 中间操作:Stream 提供了多种中间操作方法,用于对流中的元素进行处理,如 filter、map、sorted、distinct 等。
  • 终端操作:Stream 最终需要通过终端操作来触发流水线的执行,产生结果,如 forEach、collect、reduce、count 等。
  • 惰性求值:Stream 的中间操作是惰性求值的,只有遇到终端操作时才会被触发执行。
  • 并行流:Stream 提供了并行流的功能,通过并行处理数据,可以提高处理效率。

Stream 提供了一种更简洁、更高效的方式对集合进行操作,也提供了更多的操作手段来满足各种数据处理的需求。通过流式操作,可以更容易地编写出简洁、清晰的代码。

3. 创建 Stream

下面就用Java代码演示一下从不同数据源创建 Stream 的示例:

1. 从集合创建 Stream:

List<String> list = Arrays.asList("apple", "banana", "orange");
Stream<String> streamFromList = list.stream();

2. 从数组创建 Stream:

String[] array = { "apple", "banana", "orange" };
Stream<String> streamFromArray = Arrays.stream(array);

3. 从指定值创建 Stream:

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

4. 从文件创建 Stream(以文本文件为例):

Path filePath = Paths.get("c://file.txt");
try {
    Stream<String> streamFromFile = Files.lines(filePath);
} catch (IOException e) {
    e.printStackTrace();
}

5. 创建无限流(如生成一系列连续的整数):

Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 1);

4. Stream 的中间操作

使用 Stream可以通过中间操作对流中的元素进行处理和转换。以下是常见的几种中间操作方法及其作用、用法和示例代码:

filter 方法:保留符合条件的元素,丢弃不符合条件的元素。

List<String> fruits = Arrays.asList("apple", "banana", "orange", "pear", "grape");
Stream<String> filteredStream = fruits.stream().filter(fruit -> fruit.startsWith("a"));
// 过滤出以字母"a"开头的水果

map 方法:对流中的每个元素执行指定的映射函数,并将映射后的结果组成一个新的流。

List<String> fruits = Arrays.asList("apple", "banana", "orange", "pear", "grape");
Stream<Integer> lengthsStream = fruits.stream().map(String::length);
// 获取每个水果字符串的长度组成新的流

sorted 方法:用于对流中的元素进行排序。

List<String> fruits = Arrays.asList("apple", "banana", "orange", "pear", "grape");
Stream<String> sortedStream = fruits.stream().sorted(); // 自然排序
// 或者
Stream<String> sortedByLengthStream = fruits.stream().sorted(Comparator.comparingInt(String::length));
// 根据字符串长度排序

distinct 方法:用于去除流中重复的元素。

List<String> fruits = Arrays.asList("apple", "banana", "orange", "apple", "pear", "banana");
Stream<String> distinctStream = fruits.stream().distinct();
// 去除重复的水果元素

5. Stream 的终端操作

使用 Stream 可以通过终端操作方法触发流水线的执行,并产生最终结果。下面演示一下常见的几种终端操作方法及其作用、用法和示例代码:

forEach 方法:对流中的每个元素执行指定的操作。

List<String> fruits = Arrays.asList("apple", "banana", "orange", "pear", "grape");
fruits.stream().forEach(System.out::println);
// 打印每个水果元素

collect 方法:将流中的元素收集到一个集合或其他数据结构中。

List<String> fruits = Arrays.asList("apple", "banana", "orange", "pear", "grape");
List<String> collectedList = fruits.stream().collect(Collectors.toList());
// 将流中的元素收集到一个新的列表中

reduce 方法:将流中的元素反复结合起来,得到一个最终的结果值。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, Integer::sum);
// 对流中的所有元素求和,初始值为0

count 方法:返回流中元素的数量。

List<String> fruits = Arrays.asList("apple", "banana", "orange", "pear", "grape");
long count = fruits.stream().count();
// 统计流中元素的数量

6. Stream 的性能优化

在使用 Java Stream 时,下面是常用的性能优化策略:

  • 避免过度使用中间操作:过多的中间操作可能会增加额外的计算开销。应该尽量合并中间操作,或者选择合适的时机执行终端操作,以减少不必要的中间步骤。

  • 及时使用并行流:对于大型数据集,使用并行流可能提升性能。但是在小型数据集或者简单的计算中,并行流可能会增加额外的线程管理开销,导致性能下降。因此,要谨慎使用并行流,根据实际情况选择是否使用。

  • 避免自动装箱和拆箱:Stream 操作中的基本类型(int、double、long 等)会被自动装箱成对应的包装类型,这会引入额外的性能开销。如果可能,尽量使用原始类型的流(IntStream、DoubleStream、LongStream)以避免自动装箱和拆箱的开销。

  • 考虑数据结构选择:在特定场景下,选择合适的数据结构来存储数据可能会影响到性能。例如,如果需要频繁的插入和删除操作,LinkedList 可能更合适;如果需要随机访问和搜索,ArrayList 可能更优。

  • 避免过度计算:Stream 提供了延迟计算的特性,即在终端操作执行之前,中间操作不会立即执行。但是,在某些情况下可能会产生不必要的计算。避免过度计算,根据需要使用 limit()、filter() 等限制数据集大小。

  • 使用基本方法:在一些情况下,使用原始的循环和操作可能比 Stream 更高效。尤其是在性能要求较高的场景下,原始的迭代和循环可能更适合。

在代码开发过程中,可以通过测试和性能分析来验证优化策略的有效性,根据实际情况进行调整。

7. 实例演示

平时项目中经常会常会遇到一些需求,比如构建菜单,构建树形结构,数据库一般就使用父id来表示,为了降低数据库的查询压力,我们可以使用Java8中的Stream流一次性把数据查出来,然后通过流式处理。

实体类:Menu.java

/**
 * Menu
 */
@Data
@Builder
public class Menu {
    /**
     * id
     */
     public Integer id;
     /**
     * 名称
     */
     public String name;
     /**
     * 父id ,根节点为0
     */
     public Integer parentId;
     /**
     * 子节点信息
     */
     public List<Menu> childList;


    public Menu(Integer id, String name, Integer parentId) {
        this.id = id;
        this.name = name;
        this.parentId = parentId;
    }
    
    public Menu(Integer id, String name, Integer parentId, List<Menu> childList) {
        this.id = id;
        this.name = name;
        this.parentId = parentId;
        this.childList = childList;
    }

}

递归组装树形结构: 

@Test
public void testtree(){
    //模拟从数据库查询出来
    List<Menu> menus = Arrays.asList(
            new Menu(1,"根节点",0),
            new Menu(2,"子节点1",1),
            new Menu(3,"子节点1.1",2),
            new Menu(4,"子节点1.2",2),
            new Menu(5,"根节点1.3",2),
            new Menu(6,"根节点2",1),
            new Menu(7,"根节点2.1",6),
            new Menu(8,"根节点2.2",6),
            new Menu(9,"根节点2.2.1",7),
            new Menu(10,"根节点2.2.2",7),
            new Menu(11,"根节点3",1),
            new Menu(12,"根节点3.1",11)
    );

    //获取父节点
    List<Menu> collect = menus.stream().filter(m -> m.getParentId() == 0).map(
            (m) -> {
                m.setChildList(getChildrens(m, menus));
                return m;
            }
    ).collect(Collectors.toList());
    System.out.println("-------转json输出结果-------");
    System.out.println(JSON.toJSON(collect));
}

/**
 * 递归查询子节点
 * @param root  根节点
 * @param all   所有节点
 * @return 根节点信息
 */
private List<Menu> getChildrens(Menu root, List<Menu> all) {
    List<Menu> children = all.stream().filter(m -> {
        return Objects.equals(m.getParentId(), root.getId());
    }).map(
            (m) -> {
                m.setChildList(getChildrens(m, all));
                return m;
            }
    ).collect(Collectors.toList());
    return children;
}

格式化打印结果: 

 

8. 注意事项

使用 Java Stream 时需要注意以下事项:

  • 空指针异常:在使用 Stream 时,如果流中的元素可能为 null,应该小心处理空指针异常。例如,在调用 map() 或 filter() 方法时,可能会返回 null 值,导致空指针异常。

  • 懒加载特性:Stream 具有延迟计算特性,中间操作不会立即执行。如果程序没有正确使用终端操作方法来触发计算,可能会导致流水线操作不执行,进而出现预期之外的结果。

  • 并行流的使用:虽然并行流可以提高性能,但是并不是所有场景都适合使用。如果数据量不大或者计算简单,使用并行流反而可能会带来额外的性能开销。应该根据实际情况进行评估和选择。

  • 状态ful 操作:避免在并行流中使用有状态的中间操作,这可能会引发竞争条件和不确定的结果。例如,在 forEachOrdered()、sorted() 等操作中使用状态。

  • 数据源共享:避免多个线程共享可变数据源。当流是从共享的可变数据源创建时,可能会引发线程安全问题。确保数据源是线程安全的或者在流创建时进行合适的同步。

  • 使用 limit() 操作:limit() 操作可能会限制数据集的大小,但要注意在无限流中使用 limit() 可能导致无法终止的流操作。

  • 使用findFirst() 和 findAny():在并行流中使用 findFirst() 和 findAny() 可能会得到不同的结果。在并行操作时,findAny() 可能更高效,但结果并不稳定。

  • 自动装箱拆箱:Stream 操作中的基本类型(int、double、long 等)会被自动装箱成对应的包装类型。频繁的自动装箱拆箱可能会引入性能问题,应该尽量避免。

9. 结语

正确使用 Stream 可以大大简化集合操作的代码量,提高代码的可读性和维护性。但需要注意合适的使用场景和方法,避免潜在的问题,以发挥 Stream 的最大优势。希望通过本文介绍能够让大家对Java Stream的用法更加熟悉,提高自己的工作效率,今天的内容就分享到这里啦。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

苹果在美国被禁售有望反转!

都说开门大吉,可2024年似乎对苹果公司并不友好,一会儿是Apple Watch系列在美国被禁售,一会儿又是分析师唱衰iPhone 16,总之各种风声杂糅,给人一种苹果正遭遇重大危机的感觉。 此前美国国际贸易委员会(ITC)下达了对苹果旗下部分智能手表的进口禁令,Apple Watch Series9和…

优思学院|为什么医疗行业供应商需要六西格玛管理?

什么是六西格玛管理&#xff1f; 六西格玛管理是一种旨在提高产品和服务质量的方法论。它通过减少过程中的错误和变异性&#xff0c;来提高效率和性能。自20世纪80年代由摩托罗拉公司首创以来&#xff0c;六西格玛已经在各行各业得到广泛应用。 医疗行业的特点 医疗行业是一…

C#.Net学习笔记——设计模式六大原则

***************基础介绍*************** 1、单一职责原则 2、里氏替换原则 3、依赖倒置原则 4、接口隔离原则 5、迪米特法原则 6、开闭原则 一、单一职责原则 举例&#xff1a;类T负责两个不同的职责&#xff1a;职责P1&#xff0c;职责P2。当由于职责P1需求发生改变而需要修…

【leetcode】力扣热门之回文链表【简单难度】

题目描述 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 用例 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true 输入&#xff1a;head [1,2] 输出&#xff1a;f…

TikTok电商年度洞察:出海到底“卖什么”?各国多类目爆款洞察,迅速掌握市场领先优势

很多卖家在尝试出海时&#xff0c;常面临两大核心痛点&#xff1a;一是“卖什么”&#xff0c;即选择何种商品进行销售&#xff1b;二是“怎么卖”&#xff0c;即如何通过有效的营销策略将商品销售出去。TikTok主打的内容电商模式&#xff0c;通过短视频和直播等形式&#xff0…

LeetCode-1822/1502/896/13

1.数组元素积的符号&#xff08;1822&#xff09; 题目描述&#xff1a; 已知函数 signFunc(x) 将会根据 x 的正负返回特定值&#xff1a; 如果 x 是正数&#xff0c;返回 1 。 如果 x 是负数&#xff0c;返回 -1 。 如果 x 是等于 0 &#xff0c;返回 0 。 给你一个整数数组…

2.SPSS数据文件的建立和管理

文章目录 数据文件的特点建立SPSS数据文件步骤 数据文件的结构变量的规则 数据的录入和保存录入数据保存文件 数据的编辑数据定位 数据文件的特点 SPSS数据库文件包括文件结构和数据两部分 SPSS数据文件中的一列数据称为一个变量。每个变量都应有一个名称&#xff0c;即&…

如何搭建WampServer并结合cpolar内网穿透实现公网访问本地服务

文章目录 推荐前言1.WampServer下载安装2.WampServer启动3.安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3.4 创建公网地址 4.固定公网地址访问 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&am…

视图与索引连表查询内/外联和子查询

1.视图 先介绍一下视图&#xff1a; 从SQL的角度来看&#xff0c;视图和表是相同的&#xff0c;两者的区别在于表中存储的是实际的数据&#xff0c;而视图中保存的是SELECT语句&#xff08;视图本身并不存储数据&#xff09;。 使用视图可以轻松完成跨多表查询数据等复杂操作…

案例分享:当前高端低延迟视频类产品方案分享(内窥镜、记录仪、车载记录仪、车载环拼、车载后视镜等产品)

若该文为原创文章&#xff0c;转载请注明出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/135439369 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结…

Qt QWidget窗口基类

文章目录 1 QWidget介绍2 如何显示 QWidget窗口2.1 新建基于QWidget的窗口类2.2 再添加一个QWidget窗口类2.3 显示新添加的 QWidget窗口 3 常用的属性和方法3.1 窗口位置3.2 窗口大小3.3 窗口标题3.4 窗口图标3.5 资源文件 4 实例 1 QWidget介绍 Qt 中的常用控件&#xff0c;比…

flex弹性盒子常用的布局属性详解

想必大家在开发中经常会用到flex布局。而且还会经常用到 justify-content 属性实现分栏等等 接下来给大家分别讲一下 justify-content 的属性值。 以下是我敲的效果图大家可以清晰看出区别 space-between 属性值可以就是说两端对齐 space-evenly 属性值是每个盒子之间的…

觉得伺服方案比较难开发的可以看过来

参数 TMCM-1690是单轴FOC伺服电机控制模块集成预驱适合三相BLDC/PMSM和DC有刷伺服电机&#xff0c;带有高达1.5A栅极驱动电流和60V(48 V nominal)供电提供了UART(RS232-/RS485ready),CAN 和 EtherCAT 通讯接口支持TML,CANopen,或 CANopen-over-EtherCAT通讯协议TMCM-1690 支持增…

[足式机器人]Part2 Dr. CAN学习笔记 - Ch03 傅里叶级数与变换

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-Ch03 傅里叶级数与变换 1. 三角函数的正交性2. 周期为 2 π 2\pi 2π的函数展开为傅里叶级数3. 周期为 2 L 2L 2L的函数展开4. 傅里叶级数的复数形式5. 从傅里叶级数推导傅里叶变换FT6. 总结 1. …

【控制篇 / 策略】(7.4) ❀ 01. IP地理位置数据库和地理地址对象 ❀ FortiGate 防火墙

【简介】在很多使用环境下&#xff0c;我们需要对指定国家的IP地址进行允许或禁止访问操作&#xff0c;例如只允许访问国内IP。以前只能手动添加IP地址对象到地址组&#xff0c;繁杂且效率低下&#xff0c;Fortinet提供了基于地理位置的IP库&#xff0c;就可以解决这个问题。 I…

手把手教你学会接口自动化系列一-浅浅地尝试编写登录接口的自动化代码

老痞既然要带你学会接口自动化,那么老痞肯定是要把你当做0基础,从最基本的开始写起,一步步写到最后。 我最先打开项目。 第一步就是需要登录,所以这个时候,我们查看下登录的接口,我抓到接口如下: 第一个就是登录的接口,接口的地址为: http://192.168.0.134:8081/log…

【PostgreSQL创建索引的锁分析和使用注意】

1.1 创建普通B-tree索引的整体流程 如下是梳理的创建普通B-tree索引的大概流程&#xff0c;可供参考。 1.校验新索引的Catalog元数据|语法解析 ---将创建索引的sql解析成IndexStmt结构&#xff5c;校验B-Tree的handler -----校验内核是否支持该类型的索引,在pg_am中查找&q…

2024年游泳耳机十大品牌排行榜,游泳耳机哪个牌子好

游泳耳机市场正呈现蓬勃发展的趋势&#xff0c;而在众多品牌中选择一款适合自己的游泳耳机变得愈发重要。游泳不仅是锻炼身体的有效方式&#xff0c;还是缓解压力的良好途径。然而&#xff0c;游泳时的单调可能成为一些人的困扰&#xff0c;特别是那些希望在运动中欣赏音乐或聆…

二叉树的层序遍历经典问题(算法村第六关白银挑战)

基本的层序遍历与变换 二叉树的层序遍历 102. 二叉树的层序遍历 - 力扣&#xff08;LeetCode&#xff09; 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入…

25 心形按钮

效果演示 实现了一个心形的心形图案&#xff0c;当用户点击图案时&#xff0c;图案会旋转并缩小&#xff0c;同时背景颜色会变成白色。 Code <div class"love"><input id"switch" type"checkbox"><label class"love-heart&…