Java Stream后续来了,汇总一些项目开发中高频使用的 Stream操作

news2024/11/18 7:52:03

不过讲解这些操作时用的都是非常简单的例子,流操作的数据也都是简单类型的,主要的目的是让大家能更快速地理解 Stream 的各种操作应用在数据上后,都有什么效果。

在现实场景中实际做项目的时候,我们使用Stream操作的数据大多数情况下是各种业务对象 DO(Domain Object)的集合,比如用 Stream 在业务对象组成的List中筛选出符合规则的元素,按照业务对象的某个字段排序生成新的业务对象集合等等。

所以这里我们特地单独开一篇内容,汇总一下我在项目开发中曾高频使用过的几种 Stream 操作,这几个操作在实际做项目时,对操作业务对象流得到自己想要的结果非常有用,代码编写起来也非常简洁,编码体验很爽。

不过想要通过 Stream 操作拿到想要的结果,往往需要多个 Stream 操作的链式调用组合而成,再加上很多 Stream 操作步骤里都会用到 Lambda 表达式,新手乍一看到这些代码会有:“这一大坨代码都是干了啥?”的感觉,所以前面 Stream 各种基础操作以及 Lambda 表达式方面的知识大家一定要理解透了,打好基础后再来看这篇内容。

本文一共总结了四大类在项目开发中高频使用的 Stream 操作,相信大家看完,理解到 Stream 的精髓后,一定会在项目开发中举一反三,应用得得心应手。另外也欢迎大家把自己在开发项目中最常用的 Stream 操作,通过私信、评论等方式告诉我,让这篇文章能越来越完善,给更多人提供参考价值。

本文大纲如下:

求两个对象 List 的交集/差集

以对象的属性为判断,求两个 List 的交集或者差集的操作,在项目开发中挺常见的,用普通循环来实现的话,需要两层循环才能实现,如果是用 Steam 的话代码就会显得很清晰,没有那么多层级嵌套。 先说一下交集和差集的概念,其实以前中学的时候学过,这里再交代一下,防止有人搞混。 假设有两个集合,集合A中有这几个元素 [1, 2, 3, 4, 5],集合B中有 [3, 4, 5, 6, 7, 8] 这几个元素,那么两个集合的交集和差集分别是:

  • 交集:表示在两个集合中都存在的元素,所以 A、B的交集为:[3, 4, 5]
  • 差集:求差集需要看以谁为参照
    • 集合 A 与 B 的差集:在集合 A 中而不在集合 B 中的元素,所以差集是:[1, 2]
    • 集合 B 与 A 的差集:在集合 B 中而不在集合 A 中的元素,所以差集是:[6, 7, 8]

现在假设我们有这样一个 名为 Person 的业务对象的类。

package com.example.streampractice;
public class Person {
    String id;
    String nickName;

    public A(String id, String nickName) {
        this.id = id;
        this.nickName = nickName;
    }

    @Override
    public String toString() {
        return "A{" +
                "id='" + id + '\'' +
                ", nickName='" + nickName + '\'' +
                '}';
    }

    public String getId() {
        return id;
    }

    public String getNickName() {
        return nickName;
    }
}
复制代码

现在给到两个 Person对象的 List,要求以 Person 对象的 Id 为标准,求两个 List 的交集和差集。

用Stream 求两个对象List的交集

根据元素 Person 对象的Id 为判断标准,求两个List的交集,用 Stream 的实现如下:

package com.example.streampractice;

public class GetObjectListIntersection {

    public static void main(String[] args) {
        List<Person> aList = new ArrayList<>(Arrays.asList(
                new Person("1", "张三"),
                new Person("2", "李四"),
                new Person("3", "王五")
        ));

        List<Person> bList = new ArrayList<>(Arrays.asList(
                new Person("2", "李四"),
                new Person("3", "王五"),
                new Person("4", "赵六")
        ));

        // aList 与 bList 的交集 (在两个集合中都存在的元素)
        List<Person> intersections = aList
                .stream() //获取第一个集合的Stream1
                .filter(  //取出Stream1中符合条件的元素组成新的Stream2,lambda表达式1返回值为true时为符合条件
                    a ->  //lambda表达式1,a为lambda表达式1的参数,是Stream1中的每个元素
                    bList.stream() //获取第二个集合的Stream3
                            .map(Person::getId) //将第二个集合每个元素的id属性取出来,映射成新的一个Stream4
                            .anyMatch( //返回值(boolean):Stream4中是否至少有一个元素使lambda表达式2返回值为true
                                id -> //lambda表达式2,id为lambda表达式2的参数,是Stream4中的每个元素
                                Objects.equals(a.getId(), id) //判断id的值是否相等
                        	)
                )
                .collect(Collectors.toList()); //将Stream2转换为List
        System.out.println("----------aList 与 bList 的交集为:");
        System.out.println(intersections);
    }
}

复制代码

上面代码示例中的 Stream 调用因为缩进的问题显得挺吓人,不过这么缩进主要是为了写注释。在注释里,我为每个Stream步骤都做了解释,大家可以跟着注释走一遍,就不觉得这个 Stream 操作很难懂了。 运行例程会有下面输出,大家可以自己练习试一下:

----------aList 与 bList 的交集为:
[Person{id='2', nickName='李四'}, Person{id='3', nickName='王五'}]
复制代码

用 Stream 求两个对象List的差集

接下来这个例子以 Person 对象的 Id 为判断标准,求 bList 对象列表与 aList 对象列表的差集,差集的意思是,上面我们已经解释过了

List<Person> differences = bList.
    stream().
    filter(
    	b -> 
    	aList.stream()
        	.map(Person::getId)
        	.noneMatch(
                id -> 
                Objects.equals(b.getId(), id)
            )
    ).collect(Collectors.toList());

System.out.println("----------bList 与 aList 的差集为:");
System.out.println(differences);
复制代码

根据元素对象的ID求两个对象List的差集,只要把上面求交集的 Stream 操作步骤里的 anyMatch 换成 noneMatch 就可以了。

用 Stream 求集合的差集(高效版)

上面这个 Stream 操作的执行效率不高,因为 bList 中的每个元素都要在 noneMatch 里判断在 aList 里有没有跟它 Id 重复的对象,相当于整个筛选是O(N²)的复杂度, 所以如果列表的数据足够多,还是挺耗费性能的。为了减少遍历操作,我们可以先把 aList 转化成以 Person 对象 Id为 key 的 Map 容器,这样 noneMatch 里的操作只需要判断一次key存不存在即可,整个筛选变成了O(N)的复杂度。

Map<String, A> aMap = aList.stream().collect(Collectors.toMap(A::getId, Function.identity())) ;
List<A> diffEffective = bList.stream().filter(b -> !aMap.containsKey(b.getId())).collect(Collectors.toList());
System.out.println("----------bList 与 aList 的差集为:");
System.out.println(diffEffective);
复制代码

用 Stream 求交集的例子也能按同样思路优化, 这两个例子一开始选择低效版本的给大家演示主要是把求交集和差集的 Stream 步骤剥开给大家分析一下,实际开发的时候考虑性能记得要使用高效版本的。

用 Stream 中排序对象集合

接下来我们再说第二种在开发中会高频使用 Stream 完成的操作--排序对象集合,常见的是以业务对象的某个字段为标准,对一组业务对象进行排序。

比如有这样一个 Person类,有一组 Person 类的对象,放在 List 中。

public class Person {
    private Long personId;
    private String name;
    private Integer age;
    private Double salary;

    public Long getPersonId() {
        return personId;
    }

    public void setPersonId(Long personId) {
        this.personId = personId;
    }

    public Person(Long personId, String name, Integer age, Double salary) {
        this.personId = personId;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}
复制代码

要按 Person 对象的 personId 为标准,对这组对象进行排序。这个操作同样能够通过 Stream 操作完成。

按对象字段正序排序对象集合

Stream API 里有一个 sorted 方法,通过该方法我们就能Stream 元素按照对象的指定的字段进行排序。

public static void main(String[] args) {
    List<Person> personList = Arrays.asList(new Person(1000L, "First", 25, 30000D),
            new Person(2000L, "Second", 30, 45000D),
            new Person(3000L, "Third", 35, 25000D));

    // 使用字段包装类型的 compareTo 进行排序, 这个方式可以简化成下面的使用 Comparator.comparing 的例子
    personList.stream().sorted(
        (p1, p2) -> 
        (p1.getPersonId().compareTo(p2.getPersonId()))
    )
    .forEach(person -> System.out.println(person.getName()));
}
复制代码

Stream 的 sorted 方法内部,这个例子使用的是Person对象personId字段的包装类型实现的compareTo方法进行的比较。 这个方式可以进一步简化成下面的使用Comparator.comparing方法的方式:

personList.stream()
    .sorted(
        Comparator.comparing(Person::getPersonId)
    ).forEach(person -> System.out.println(person.getName()));
复制代码

Comparator.comparing()该方法是一个泛型方法,会根据参数的类型,内部调用相应类型实现的CompareTo方法进行两个参数的比较。

上面两个例子是把Stream里的元素按照Person对象personId的正序进行排序,运行例程后会看到下面的输出,按照personId的正序输出了每个Person对象的名字。

First
Second
Third
复制代码

用对象字段倒序排列对象集合

如果需要按照倒序对Stream中的元素进行排列,可以在Comparator.comparing()后面追加reversed()调用

personList.stream().sorted(
    Comparator.comparing(
        Person::getPersonId).reversed()
    ).forEach(person -> System.out.println(person.getName()));
复制代码

这两个例子都是用对象的单个字段,对对象集合进行排序,其实还可以让集合按照多字段排序规则进行排序。

用对象多个字段排序对象集合

Streamsorted步骤里使用Comparator.comparing()的好处是,除了倒序排序非常方便,我们还可以根据多字段进行排--比如先按Person对象的personId字段,如果再按Person对象的年龄age字段进行排序。这个时候可以在Comparator.comparaing调用后,再接上thenComparing调用,实现元素按照多字段进行排序的功能。

public static void main(String[] args) {
    List<Person> personList = Arrays.asList(new Person(1000L, "First", 25, 30000D),
                new Person(1000L, "Second", 22, 45000D),
                new Person(3000L, "Third", 35, 25000D));
    // 先按personId 排序,再按 age 排序
    personList.stream().sorted(
        Comparator.comparing(Person::getPersonId)
        .thenComparing(Person::getAge)
    ).forEach(person -> System.out.println(person.getName()));
复制代码

为了测试排序效果, 我们把List中的头两个Person对象,设置成了personId一样,年龄不同,执行下例程会看到元素排序后,打印出的Person对象Name值为:

Second
First
Third
复制代码

符合我们的预期,元素先按照 Person 对象的 personId 进行了排序,因为 personList 中前两个对象的 PersonId 相等,就继续按照 age 字段进行了排序,第二个对象的年龄更小,所以程序的输出的姓名里 Second 会排在第一位。

在Stream 迭代中使用元素索引

在 Stream 操作中没办法像 For 循环那样拿到当前迭代的元素的自然索引,虽然 Java 11 里有办法可以当前迭代元素的自然索引,因为国内大部分公司的 JDK 版本还是 8 ,所以只能另外用其他办法。

有另外一个办法,程序可以借助AtomicInteger的获取并自增方法getAndIncrement方法实现在 Stream 迭代中拿到当前迭代元素的自然索引的效果,看下面这个示例程序。

import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;

// Java8 里没办法直接访问 stream 里的元素,不过可以用下面这种方式实现
public class AtomicIntegerAsStreamIndices {
    public static void main(String[] args)
    {
        String[] array = { "A", "B", "C", "D"  };

        AtomicInteger index = new AtomicInteger();
        Arrays.stream(array)
                .map(str -> index.getAndIncrement() + " -> " + str)
                .forEach(System.out::println);
    }
}

复制代码

该例程会输出:

0 -> A
1 -> B
2 -> C
3 -> D
复制代码

把对象 List 转换为对象 Map

最后这个 Stream 操作,在开发中使用的也非常多,一般为了提高程序效率的时候,我们会把对象 List 转换成以对象 Id 为 Key 的对象 Map。用 Stream 我们能简单便捷的把 List 转换成 Map ,免去了在程序里写两层循环的窘境。 假设我们有这样一个 Animal 类:

public class Animal {
    private int id;
    private String name;

    public Animal(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
复制代码

接下来,我们使用 Stream 操作把 Animal 对象的 List 转换成以对象 id 为 Key 的对象 Map。

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

public class ListStreamToMap {


    public static void main(String[] args) {
        List<Animal> aList = new ArrayList<>();
        aList.add(new Animal(1, "Elephant"));
        aList.add(new Animal(2, "Bear"));

        Map<Integer, Animal> map = aList.stream()
                .collect(Collectors.toMap(Animal::getId, Function.identity()));

        map.forEach((integer, animal) -> {
            System.out.println(animal.getName());
        });

    }
}
复制代码

总结

本文一共总结了四大类在项目开发中高频使用的 Stream 操作,相信大家看完,理解到 Stream 的精髓后,一定会在项目开发中举一反三,应用得得心应手。另外也欢迎大家把自己在开发项目中最常用的 Stream 操作,通过私信、评论等方式告诉我,让这篇文章能越来越完善,给更多人提供参考价值。

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

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

相关文章

OpenCV颜色识别

颜色分辨 单个颜色识别 代码 import cv2 import numpy as npdef color(lower, upper, name):Img cv2.imread(image/origin/all.png) # 读入一幅图像kernel_3 np.ones((3, 3), np.uint8) # 3x3的卷积核if Img is not None: # 判断图片是否读入HSV cv2.cvtColor(Img, cv2…

maven中profiles使用详解,多环境开发配置文件(开发,测试,生产)+ pom中resources部分标签介绍

一.maven中profiles使用详解&#xff08;仅供参考&#xff09; 使用的场景 常常遇到一些项目中多环境切换的问题。比如在开发过程中用到开发环境&#xff0c;在测试中使用测试环境&#xff0c;在生产中用生产环境的情况。springboot中提供了 spring.profile.active的方式来实…

以mariadb为例介绍如何使用systemctl命令集设置服务开机自启动

以mariadb为例介绍如何使用systemctl命令集设置服务开机自启动一、systemd简介二、systemctl命令集常用命令三、以mariadb自启动为例四、更多说明一、systemd简介 systemd即为system daemon,是linux下的一种init软件,由Lennart Poettering带头开发,并在LGPL 2.1及其后续版本许…

[思维模式-13]:《复盘》-1- “知”篇 - 认识复盘

目录 前言 一、什么是复盘 二、复盘的三个关键词 三、复盘&#xff0c;而非总结 四、复盘的优势与局限 五、复盘与行动学习、培训、绩效改进的区别与联系 六、关于复盘的几个常见误解 误解1&#xff1a;可否对他人之事进行复盘 误解2&#xff1a;“项目后评估”是复盘吗…

细粒度图像分类模型(含实战代码)

来源&#xff1a;投稿 作者&#xff1a;lsc 编辑&#xff1a;学姐 理论部分 01细粒度图片分类问题 1.1细粒度图片分类特点 可判别区域往往只是在图像中很小的一块区域内。 1.2细粒度图像分类数据集 1.3细粒度图像分类竞赛 1.4细粒度图像分类模型分类: (1)强监督模型: 需要…

Java之AQS

AQS是什么 是用来实现锁或者其它同步器组件的公共基础部分的抽象实现&#xff0c;整体就是一个抽象的FIFO队列来完成资源获取线程的安排工作&#xff0c;并通过一个int类变量表示持有锁的状态。 使用到AQS的一些类 ReentranLock: CountDownLatch ReentrantReadWriteLock:…

Go语言开发小技巧易错点100例(四)

往期回顾&#xff1a; Go语言开发小技巧&易错点100例&#xff08;一&#xff09;Go语言开发小技巧&易错点100例&#xff08;二&#xff09;Go语言开发小技巧&易错点100例&#xff08;三&#xff09; 本期看点&#xff08;技巧类用【技】表示&#xff0c;易错点用…

_14LeetCode代码随想录算法训练营第十四天-C++二叉树

_14LeetCode代码随想录算法训练营第十四天-C二叉树 题目列表 104.二叉树的最大深度559.n叉树的最大深度111.二叉树的最小深度222.完全二叉树的节点个数 104.二叉树的最大深度 题目 给定一个二叉树&#xff0c;找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长…

RabbitMQ 第一天 基础 3 RabbitMQ 快速入门 3.1 入门程序【生产者】

RabbitMQ 【黑马程序员RabbitMQ全套教程&#xff0c;rabbitmq消息中间件到实战】 文章目录RabbitMQ第一天 基础3 RabbitMQ 快速入门3.1 入门程序3.1.1 生产者第一天 基础 3 RabbitMQ 快速入门 3.1 入门程序 3.1.1 生产者 看下文档 点进去 先就来做一个 这个简单模式 P&…

vector

目录vector的介绍和使用vector的介绍vector的使用vector 空间增长问题vector 迭代器失效问题。&#xff08;重点&#xff09;vector与erase迭代器失效的代码vector深度剖析及模拟实现vector模拟实现代码使用memcpy拷贝问题动态二维数组理解vector反向迭代器reverse_iteratorvec…

Android开发进阶——Coil对比Glide分析

Coil概述 Coil是Android上的一个全新的图片加载框架&#xff0c;它的全名叫做coroutine image loader,即协程图片加载库。 与传统的图片加载库Glide&#xff0c;Picasso或Fresco等相比。该具有轻量&#xff08;只有大约1500个方法&#xff09;、快、易于使用、更现代的API等优…

【Vue项目搭建】vue-admin-template修改(2)

接上文、、 --------------------------------------------------------- 优化登录 单独封装路由守卫 &#xff0c;设置白名单&#xff0c;permission.js&#xff0c;鉴权 跳转动画优化&#xff08;使用NProgress插件&#xff09; 显示logo svg 改填充颜色 stroke //画线颜色…

小题 错题总结

要是对象具有序列化&#xff0c;应该实现的接口是 Java.IO.Serializable在 JVM 内存划分中 &#xff0c;方法通常存储在 方法区多态的3种表现形式&#xff1a; 继承重写 重载 向上转型Java 中继承可以间接继承&#xff0c;即便中间跨过一个类&#xff0c;栗子&#xff1a;所有…

一文读懂Linux内核中的Device mapper映射机制

本文结合具体代码对 Linux 内核中的 device mapper 映射机制进行了介绍。Device mapper 是 Linux 2.6 内核中提供的一种从逻辑设备到物理设备的映射框架机制&#xff0c;在该机制下&#xff0c;用户可以很方便的根据自己的需要制定实现存储资源的管理策略&#xff0c;当前比较流…

基于PHP的动漫电影信息管理系统

有需要请私信或看评论链接哦 可远程调试 基于PHP的动漫电影管理系统一 介绍 此动漫电影信息管理系统基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。系统角色分为用户和管理员&#xff0c;用户注册登录后可观看/下载/收藏/留言/评分动漫电影等&#xff0c…

Multi-Channel PCe QDMARDMA Subsystem

可交付资料&#xff1a; 1. 详细的用户手册 2. Design File&#xff1a;Post-synthesis EDIF netlist or RTL Source 3. Timing and layout constraints&#xff0c;Test or Design Example Project 4. 技术支持&#xff1a;邮件&#xff0c;电话&#xff0c;现场&…

隐私计算概述

1. 基本概念 隐私计算是指在保证数据提供方不泄露原始数据的前提下,对数据进行分析计算的一些列信息技术,保障数据在流通和融合过程中的“可用不可见”。 从技术交付出发,隐私计算是众多学科的交叉融合技术,目前主流的隐私计算技术分为三大方向:第一类是多方安全计算为代…

linux内核中断

目录 硬中断特点 中断API 线程中断 系统标准的优先级顺序 中断信息查看 中断上半部与下半部 软中断与并发 硬中断特点 优先级最高中断函数在中断上下文中&#xff0c;不能阻塞不要间接或直接调用shedule() 在申请内存空间时&#xff0c;使用GFP_ATOMIC 标志&#xff08…

Blender——苹果纹理绘制

效果图 前言 在进行纹理绘制之前&#xff0c;首先要具有苹果三维模型。 关于苹果的建模请参考&#xff1a;Blender——“苹果”建模_行秋的博客 1.苹果UV的展开 1.1首先点击UV Eidting&#xff0c;滑动三维模型&#xff0c;使其大小适中。 1.2打开左上角的UV选区同步&#x…

IPv6 的地址(计算机网络-网络层)

目录 IPv6地址的表示方法 IPv6的分类 IPv6 全球单播地址 IPv6 多播地址 IPv6地址的表示方法 在 IPv6 中&#xff0c;每个地址占 128 位&#xff0c;地址空间大于 3.4 *10^ 38 。在想象得到的将来&#xff0c;IPv6的地址空间是不可能用完的 128位的IPv6地址使用冒号十六进制记…