JAVA混合使用函数式接口(BiPredicate和Consumer)、泛型、lambda表达式、stream流,优化List求交集和差集后的通用处理

news2024/11/20 15:30:45

CSDN成就一亿技术人

文章目录

  • 前言
  • 项目场景
  • 两个List求交集和差集
  • BiPredicate和Consumer基本介绍
  • 优化目标
  • 一步步优化代码
  • 最后


前言

本文主要讲的是一个小的功能代码的优化案例,用到的知识点主要包括函数式接口(BiPredicate和Consumer)泛型lambda表达式stream流。主要目的是提高代码质量,减少 “流水账” 的重复代码,提高可读性和可维护性。实现的功能是:对比两个嵌套List,求交集和差集,并对交集和差集做对应的消费处理。希望能以此抛转引玉扩展大家使用 函数式接口的场景。


项目场景

项目场景比较像俄罗斯套娃,我用例子模拟的类嵌套关系如下:

A1里有List<B1>,B1里又有List<C1>, C1里又有List<D1>

A2里有List<B2>,B2里又有List<C2>, C2里又有List<D2>

@Data
public class A1 {
    private Integer id;
    // 其它字段...
    private List<B1> b1List;

    @Data
    public static class B1 {
        private Integer id;
        // 其它字段...
        private List<C1> c1List;

        @Data
        public static class C1 {
            private Integer id;
            // 其它字段...
            private List<D1> d1List;

            @Data
            public static class D1 {
                private Integer id;
                // 其它字段...
            }
        }
    }
}

实际情况是:A1 a1前端调用API的参数对象, A2 a2后端获取的数据库对象,然后对比a1和a2(包括对比嵌套的List中的对象),凡是a1比a2少则新增,多则删除。

为什么会有这么变态的需求?

实际这是一个设计问题:产品和交互的设计。常规做法是界面的操作都会直接调用后端API,但这个案例有点特殊:界面上的操作不直接调用API,而是仅当点击【保存】按钮时才提交API,前端传给后端的是最终操作结果,所以后端需要对比处理界面最新结果与数据库结果,从而得到上面提到的逻辑。


两个List求交集和差集

咱们先实现功能,再谈代码如何优化

如下图,交集和差集简单带一下,我们要做的是:根据集合A和集合B得到以下3个集合,然后做对应处理

  • 集合A和集合B的交集:5,6
  • 集合A独有:集合C
  • 集合B独有:集合D

求集合交集差集

实现例子是对象,不是简单的数字,另外因为是不同对象类型,所以我们需要明确一下不同对象类型如何“相等”,这里的“相等”是指:id相等即对象相等: p1.getId().equals(p2.getId())

处理方式,可以使用stream方式,也可以使用传统的for循环,因为stream方式更简洁,所以推荐使用。

  • 求集合A(aList)和集合B(bList)的交集,2个stream代替2个for循环,filter是过滤,anyMatch是有任意匹配
// 循环aList, 过滤出id在bList里的对象
aList.stream().filter(p1 -> bList.stream().anyMatch(p2 -> p1.getId().equals(p2.getId())))
  • 求aList独有的,不在bList中的noneMatch是没有任何匹配,与anyMatch刚好相反
// 循环aList, 过滤出id不在bList里的对象
aList.stream().filter(p1 -> bList.stream().noneMatch(p2 -> p1.getId().equals(p2.getId())))
  • 求bList独有的,不在aList中的,和上例类似:
// 循环bList, 过滤出id不在aList里的对象
bList.stream().filter(p1 -> aList.stream().noneMatch(p2 -> p1.getId().equals(p2.getId())))

BiPredicate和Consumer基本介绍

凡是带有@FunctionalInterface注解接口都属于函数式接口,主要作用是可以将方法当做方法的参数,并且可以被隐式转换lambda 表达式,所以很常用,这里主要使用BiPredicateConsumer:

  • BiPredicate 两个参数的断言,返回boolean类型,原型:

    boolean test(T t, U u);
    

    这里主要用于断言两个对象是否相等,所以只需要1个BiPredicate

  • Consumer 消费一个对象参数,原型:

    void accept(T t);
    

    这里主要用于消费结果,因为最终有3个集合,所以需要3个Consumer


优化目标

从上面的代码,我们就可以求出交集和差集,也就很容易写出具体实现的代码,但是如果不加任何技巧的话,可能写出的就会是“流水账”代码,不仅长,还带有大量的类似代码,在CV开发时一不小心就容易写错,写完自测、修改、维扩等都是很不好的体验。所以,我们优化一下代码,提高可读性和可维护性,这里定2个目标

  • 目标1:对现有定义好的类对象无侵入,像类A1,A2,B1,B2,C1,C2,D1,D2

  • 目标2:通用,不能写死类型,也不能写死要对比的字段;


一步步优化代码

先定义通用方法,这里为了通用就需要使用泛型方法,因为是两个List,所以定义两个类型:T1, T2

方法需要的参数:

  • 2个List:aList,bList
  • 1个BiPredicate:isEqualPredicate,用于判断T1和T2是否相等
  • 3个Consumer:用于消费得到的3个集合

最终方法定义如下:

public <T1, T2> void compareList(List<T1> aList, List<T2> bList
       , BiPredicate<T1, T2> isEqualPredicate
       , Consumer<List<T1>> onlyAListHaveConsumer
       , Consumer<List<T2>> onlyBListHaveConsumer
       , Consumer<List<T1>> bothHaveConsumer) {
}

方法没有返回值,主要是处理Consumer

结合上面的基础代码,我们先实现一个 集合A独有的Consumer: onlyAListHaveConsumer

if (onlyAListHaveConsumer != null) {
    // aList独有的,不在bList中的
    List<T1> onlyAListHaveList = aList.stream().filter(p1 ->
        bList.stream().noneMatch(p2 -> isEqualPredicate.test(p1, p2)))
        .collect(Collectors.toList());
    if (onlyAListHaveList.size() > 0) {
        onlyAListHaveConsumer.accept(onlyAListHaveList);
    }
}

一共就2步:过滤出onlyAListHaveList,再调用onlyAListHaveConsumer.accept(onlyAListHaveList)

同理,完整的通用封装代码如下:

public <T1, T2> void compareList(List<T1> aList, List<T2> bList
       , BiPredicate<T1, T2> isEqualPredicate
       , Consumer<List<T1>> onlyAListHaveConsumer
       , Consumer<List<T2>> onlyBListHaveConsumer
       , Consumer<List<T1>> bothHaveConsumer) {
    if (onlyAListHaveConsumer != null) {
        // aList独有的,不在bList中的
        List<T1> onlyAListHaveList = aList.stream().filter(p1 ->
            bList.stream().noneMatch(p2 -> isEqualPredicate.test(p1, p2)))
            .collect(Collectors.toList());
        if (onlyAListHaveList.size() > 0) {
            onlyAListHaveConsumer.accept(onlyAListHaveList);
        }
    }
    if (onlyBListHaveConsumer != null) {
        // bList独有的,不在aList中的
        List<T2> onlyBListHaveList = bList.stream().filter(p2 ->
            aList.stream().noneMatch(p1 -> isEqualPredicate.test(p1, p2)))
            .collect(Collectors.toList());
        if (onlyBListHaveList.size() > 0) {
            onlyBListHaveConsumer.accept(onlyBListHaveList);
        }
    }
    if (bothHaveConsumer != null) {
        // aList和bList的交集
        List<T1> bothHaveList = aList.stream().filter(p1 ->
            bList.stream().anyMatch(p2 -> isEqualPredicate.test(p1, p2)))
            .collect(Collectors.toList());
        if (bothHaveList.size() > 0) {
            bothHaveConsumer.accept(bothHaveList);
        }
    }
}

调用的代码如下:

private void compareAndUpdateB(List<A1.B1> b1List, List<B2> b2List) {
    compareList(b1List, b2List, (p1, p2) -> p1.getId().equals(p2.getId())
            , onlyB1HaveList -> {
                // 具体处理代码
            }, onlyB2HaveList -> {
                // 具体处理代码
            }, bothHaveList -> {
                // 具体处理代码
            });
}

这里也可以将每个consumer单独提成一个方法,然后传入方法,例如定义一下方法(与Consumer方法签名相同):

public void handleOnlyB1HaveList(List<A1.B1> onlyB1HaveList) {
    // 具体逻辑
}

调用时传入this::handleOnlyB1HaveList,代码如下:

private void compareAndUpdateB(List<A1.B1> b1List, List<B2> b2List) {
    compareList(b1List, b2List, (p1, p2) -> p1.getId().equals(p2.getId())
            , this::handleOnlyB1HaveList
            , onlyB2HaveList -> {
                // 具体处理代码
            }, bothHaveList -> {
                // 具体处理代码
            });
}

最后

函数式接口(Predicate、Consumer、Function、Supplier,以及相应扩展)泛型lambda表达式stream流,这些在实际开发中非常常用,所以掌握它并灵活应用非常重要!有很多混合应用的场景,本文只以一个小的功能优化代码为例,希望能对大家有所帮助!

❤️ 博客主页:https://blog.csdn.net/scm_2008
❤️ 欢迎点赞👍 收藏留言✏️ 如有错误敬请指正!
❤️ 本文由 天罡gg 原创,首发于 CSDN博客🙉
❤️ 停下休息的时候不要忘了别人还在奔跑,希望大家抓紧时间学习,全力奔赴更美好的生活

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

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

相关文章

100天精通Python(数据分析篇)——第73天:Pandas文本数据处理方法之查找、替换、拼接、正则、虚拟变量

文章目录每篇前言一、Python字符串内置方法1. 文本查找2. 文本替换3. 文本拼接4. 正则提取二、Pandas实现文本查找1. str.startswith(字符串)2. str.endswith(字符串)3. str.index(字符串, start0, endlen(string))4. str.rindex(字符串, start0, endlen(string))5. str.find(字…

工具技巧和读文档 | 读函数式编程接口文档 | 匿名内部类 | lambda表达式 |IDEA

Function接口&#xff0c;函数式接口 按入参返回值分类&#xff0c;大概分为4种类型&#xff0c;再加上多个入参就又多了Bi开头的两种。 有CtrlP的时候不懂参数列表该写啥&#xff0c;就先CtrlALT看下入参类型的相关实现类&#xff01; 一些实用的快捷键&#xff1a;Ctrl P看参…

AORT:一款功能强大的多合一网络侦查与数据收集工具

关于AORT AORT是一款功能强大的多合一网络侦查与数据收集工具&#xff0c;该工具的主要目的是帮助漏洞Hunter和渗透测试人员完成网络侦查环节的各类任务。AORT基于Python开发&#xff0c;使用起来非常简单且方便&#xff0c;并且支持跨平台&#xff08;只要安装了Python 3即可…

怒删虚拟机,FPGA开发新宠-几步在Windows上安装桌面化Linux

Linux上运行Vivado这类EDA工具要比Window上快很多&#xff0c;大概就是优化的问题&#xff0c;所以选择Linux上开发是一个比较好的选择&#xff08;主要是免费&#xff09;。国内习惯了Win系统&#xff0c;所以用Linux比较少&#xff0c;那么有没有既可以在Windows上做一些文档…

Python爬虫(4)-Selenium模拟鼠标操作

在Selenium4.2以后的版本里面鼠标的操作方法都封装在了ActionChains中需要时直接取即可。 1.鼠标双击 使用方法就是调用ActionChains然后传入你需要点击的按钮的位置即可 ActionChains(driver).double_click(f1).perform() perform()的意思就是执行所有ActionChains中的动作 …

python字典应用

python字典应用 文章目录python字典应用一、实验目的二、实验原理三、实验环境四、实验内容五、实验步骤字典的综合案例使用collections模块的defaultdict类来实现创建字典。拓展知识一&#xff1a;内置函数globals()和locals()拓展知识二&#xff1a;有序字典 collections.Ord…

如何使用MyBatis简化JDBC开发?MyBatis持久层框架快速入门

文章目录1. 前言2. JDBC 存在的缺点3. MyBatis 优化4. MyBatis 快速入门5. 总结Java编程基础教程系列1. 前言 JavaEE 企业级 Java 项目中的经典三层架构为表现层&#xff0c;业务层和持久层&#xff0c;使用Java 代码操作数据库属于持久层内容&#xff0c;而 MyBatis 对 JDBC …

Multus k8s网络浅谈

Multus是什么 k8s不提供网络解决方案&#xff0c;提供CNI ( Container Networking Interface )规范&#xff0c;被CNI插件遵守&#xff08;Flannel, Calico&#xff0c;Multus等&#xff0c;这些是网络方案&#xff09; Multus 提供了将多个接口添加到pod的功能 Flannel 为每…

实现自己的数据库三

一 前言上篇实现了数据库的持久化&#xff0c;就是一个质的飞跃&#xff0c;虽然代码不复杂&#xff0c;但是对没有这方面经验者来说&#xff0c;还是意思的&#xff0c;下一步就是要完成另外一个飞跃&#xff0c;将存储的数据结构采用B树的形式来保存。在改造之前&#xff0c;…

为什么要设计非公平锁?

背景 公平&#xff1a;排队 非公平&#xff1a;在合适时机插队 非公平还是 ReentrantLock 的默认策略&#xff0c;排队时间不浪费了&#xff1f; 场景 来看这种场景 假如A持有一把锁&#xff0c;B请求这把锁&#xff0c;这时候B被挂起进入阻塞&#xff0c;A释放锁的时候&a…

点与线段的关系

点与线段的关系 对于向量a(x1,y1)和b(x2,y2) 点乘的数学意义&#xff1a;a * b x1x2 y1y2 点乘的几何意义&#xff1a;a * b |a||b|cosQ 这个可以看成是投影关系表达式&#xff1a;cosQ a * b / ( |a||b|) 令r cosQ&#xff1b; 求p点和线段AB的位置关系&#xff0c;可以…

将字符串根据指定的分隔符拆分为元组str.partition()

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 将字符串根据指定的分隔符拆分为元组 str.partition() 选择题 对于以下python代码最后输出的结果是? string "I Love Python" print("【显示】string.partition(Love)"…

Unity3d 微信小程序(小游戏)项目实现流量主接入功能(含源码)

前言 很早之前编写了Unity导出微信小游戏的博客&#xff0c;也尝试自己做了个Demo上线了&#xff0c;基本没更新过&#xff0c;不过几个月的时间&#xff0c;用户超过了一千&#xff0c;可以开通流量主了&#xff0c;大概率是因为上篇的帖子浏览量大了&#xff0c;扫码体验的人…

Aurora、Chip2chip、Ethernet(二)

摘要&#xff1a;Aurora、Chip2chip、Ethernet共用一个gt时钟的正确的解决方案以及在实际实现以及在实现过程中遇到的其它的问题。 我在实际中遇到的困难如下&#xff1a; 现在需要将三个ip共用一对GT时钟&#xff0c;一个Ethernet IP&#xff0c;一个Ethernet IP&#xff08…

每日学术速递1.29

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1. Compact Transformer Tracker with Correlative Masked Modeling 标题&#xff1a;带有相关掩码建模的紧凑型变压器跟踪器 作者&#xff1a; Zikai Song, Run Luo, Junqing Yu, Y…

GLM国产大模型训练加速:性能最高提升3倍,显存节省1/3,低成本上手

作者&#xff5c;BBuf、谢子鹏、冯文 2017 年&#xff0c;Google 提出了 Transformer 架构&#xff0c;随后 BERT 、GPT、T5等预训练模型不断涌现&#xff0c;并在各项任务中都不断刷新 SOTA 纪录。去年&#xff0c;清华提出了 GLM 模型&#xff08;https://github.com/THUDM…

一句话说明线程和进程

知识点&#xff1a; 1、一句话说明线程和进程 2、操作系统为什么需要进程 3、为什么要引入线程 4、一图说明线程和进程的关系 一、一句话说明线程和进程 进程&#xff1a;是指⼀个内存中运⾏的应⽤程序&#xff0c;比如QQ、微信、浏览器等&#xff1b;⼀个应⽤程序可以同时运⾏…

CAS 和 Synchronized优化过程以及常见的锁策略

目录 &#x1f411;今日良言:追星赶月莫停留,平芜尽处是春山 &#x1f402;一、锁策略 &#x1f43c;二、CAS &#x1f42d;三、Synchronized &#x1f411;今日良言:追星赶月莫停留,平芜尽处是春山 &#x1f402;一、锁策略 锁策略是实现锁的时候,考虑出现锁竞争了该怎么…

电驱系统电磁兼容基础知识及测试方法

电驱系统电磁兼容基础知识及测试方法 学习参考&#xff1a;驱动视界公众号、百度百科、《电动汽车电机驱动系统EMC研究综述》 1.背景 2.电磁干扰三要素 3.电波暗室与屏蔽室的原理 4.测试方法 5.如何看测试数据 6.工作中需要注意的EMC的几点问题 7.案例 1.背景 汽车工业发展…

docker安装db2

第一步&#xff1a;下载镜像 docker pull ibmcom/db2express-c:latest备注&#xff1a;docker images -a 可以查看已安装镜像&#xff1b; 第二步&#xff1a;启动镜像 docker run -d --name db2 -p 50000:50000 -e DB2INST1_PASSWORD[数据库密码] -e LICENSEaccept ibmcom…