如果还不懂如何使用 Consumer 接口,来公司我当面给你讲!

背景

没错,我还在做 XXXX 项目,还在与第三方对接接口,不同的是这次是对自己业务逻辑的处理。

在开发过程中我遇到这么一个问题:

表结构:
一张主表A ,一张关联表B ,表 A 中存储着表 B 记录的状态。

场景:
第一步创建主表数据,插入A表;第二步调用第三方接口插入B表同时更新A表的状态。此时大家应该都会想到在进行第二步的时候需要做好数据的幂等性。这样的话就会存在以下几种情况:

一、B表中不存在与A表关联的数据,此时需要调用第三方接口,插入B表同时更新A表的状态;

二、B表中存在与A表关联的数据;

  1. A表中的状态为处理中:直接返回处理中字样;
  2. A表中的状态为处理成功:直接返回成功的字样;
  3. A表中的状态为处理失败:此时需要调用第三方接口,更新B表同时更新A表的状态;

代码实现

首先我是这样编写的 伪代码

B b = this.baseMapper.selectOne(queryWrapper);
if (b != null) {
    String status = b.getStatus();
    if (Objects.equals(Constants.STATUS_ING, status)){
        return "处理中";
    } else if (Objects.equals(Constants.STATUS_SUCCESS, status)){
        return "处理成功";
    }
    //失败的操作
    //请求第三方接口并解析响应结果
    ......
    if (ReturnInfoEnum.SUCCESS.getCode().equals(parse.getCode())) {
        ......

        //更新B表操作
        bb.setStatus(Constants.STATUS_ING);
        mapper.updateById(bb);

        //更新A表的状态
        a.setStatus(Constants.STATUS_ING);
        aMapper.updateById(a);
    }

} else {
    //请求第三方接口并解析响应结果
    ......
    if (ReturnInfoEnum.SUCCESS.getCode().equals(parse.getCode())) {
        ......

        //插入B表操作
        bb.setStatus(Constants.STATUS_ING);
        mapper.insert(bb);

        //更新A表的状态
        a.setStatus(Constants.STATUS_ING);
        aMapper.updateById(a);
    }
}

不知道细心的小伙伴是否发现,存在B表记录并且状态为”失败”的情况和不存在B表的情况除了插入B表或者更新B表的操作之外,其余的操作都是相同的。

如果我们想要将公共的部分抽取出来,发现都比较零散,还不如不抽取,但是不抽取代码又存在大量重复的代码不符合我的风格。于是我便将手伸向了 Consumer 接口。

更改之后的伪代码

B b = this.baseMapper.selectOne(queryWrapper);
if (b != null) {
    String status = b.getStatus();
    if (Objects.equals(Constants.STATUS_ING, status)){
        return "处理中";
    } else if (Objects.equals(Constants.STATUS_SUCCESS, status)){
        return "处理成功";
    }
    //失败的操作
    getResponse(dto, response, s -> mapper.updateById(s));
} else {
    getResponse(dto, response, s -> mapper.updateById(s));
}

public void getResponse(DTO dto, Response response, Consumer consumer){
    //请求第三方接口并解析响应结果
    ......
    if (ReturnInfoEnum.SUCCESS.getCode().equals(parse.getCode())) {
        ......
        bb.setStatus(Constants.STATUS_ING);

        consumer.accept(bb);

        //更新A表的状态
        a.setStatus(Constants.STATUS_ING);
        aMapper.updateById(a);
    }
}

看到这,如果大家都已经看懂了,那么恭喜你,说明你对 Consumer 的使用已经全部掌握了。如果你还存在一丝丝的疑虑,那么就接着往下看,我们将介绍一下四种常见的函数式接口。

如果还不懂如何使用 Consumer 接口,来公司我当面给你讲!

函数式接口

那什么是函数式接口呢?函数式接口是只有一个抽象方法(Object的方法除外),但是可以有多个非抽象方法的接口,它表达的是一种逻辑上的单一功能。

@FunctionalInterface

@FunctionalInterface 注解用来表示该接口是函数式接口。它有助于及早发现函数式接口中出现的或接口继承的不适当的方法声明。

如果接口用该注解来注释,但实际上不是函数式接口,则会在编译时报错。

Consumer

我们一般称之为”消费者”,它表示接受单个输入参数但不返回结果的操作。不同于其它函数式接口,Consumer 预期通过副作用进行操作。

那什么又是副作用呢?说一下我所理解的副作用,副作用其实就是一个函数是否会修改它范围之外的资源,如果有就叫有副作用,反之为没有副作用。比如修改全局变量,修改输入参数所引用的对象等。

@FunctionalInterface
public interface Consumer {

    /**
     *  对给定的参数执行此操作。
     */
    void accept(T t);

    /**
     *
     *  返回一个组合的 Consumer ,依次执行此操作,然后执行after操作。
     *  如果执行任一操作会抛出异常,它将被转发到组合操作的调用者。
     *  如果执行此操作会引发异常,则不会执行after操作。
     */
    default Consumer andThen(Consumer after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

正如我们案例中遇到的场景,我们只需要将 要执行的逻辑方法当作参数传入 getResponse() 中,然后在该方法中执行 accept() 方法进行消费即可。如果还不理解,我们可以把它转换为匿名内部类的调用方式。

 getResponse(dto, response, new Consumer() {
    @Override
    public void accept(B bb) {
      mapper.insert(bb);
    }
});

当调用 accept() 方法的时候就会去调用匿名内部类的方法了,也就是我们传入 getResponse() 的逻辑方法。

Supplier

我们一般称之为”生产者”,没有参数输入,但是能返回结果,为结果的提供者。

@FunctionalInterface
public interface Supplier {

    /**
     *  获取一个结果
     */
    T get();
}

可以举个简单的例子感受下:

Optional optional = Optional.empty();
optional.orElseGet(()->Math.random() );

//orElseGet 方法的源码,里边用到了 get 方法
public T orElseGet(Supplier other) {   
    return value != null ? value : other.get();
}

Function

我把它称为”转换者”,表示接收一个参数通过处理之后返回一个结果的函数。

@FunctionalInterface
public interface Function {

    /**
     *  将 T 类型的参数传入,经过函数表达式的计算,返回 R 类型的结果
     */
    R apply(T t);

    /**
     * 返回一个组合函数,先将参数应用于 before 函数,然后将结果应用于当前函数,返回最终结果。
     * 如果对任一函数的求值引发异常,则会将其转发给组合函数的调用方。
     */
    default  Function compose(Function before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    /**
     * 返回一个组合函数,先将参数应用与当前函数,然后将结果应用于 after 函数,返回最终的结果。
     * 如果对任一函数的求值引发异常,则会将其转发给组合函数的调用方。
     */
    default  Function andThen(Function after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    /**
     *  返回始终返回其输入参数的函数。
     */
    static  Function identity() {
        return t -> t;
    }
}

我们在 lambda 表达式中应用比较多,所以我们来简单演示下:

@Data
@AllArgsConstructor
public class Teacher {
    private String name;
    private int age;
}

public class TeacherTest {
    public static void main(String[] args) {
       List list = Arrays.asList(
            new Teacher("张三",25),
            new Teacher("李四",28),
            new Teacher("王五",18));
      List collect = list.stream().map(item -> item.getName()).collect(Collectors.toList());
      System.out.println(collect);
    }
}

其中 map 接收的参数就是 Function 类型, item 为传入参数, item.getName() 为返回处理的结果,最后输出结果为

[张三, 李四, 王五]

Predicate

我们称之为”判断者”,通过接收参数 T 来返回 boolean 的结果。

@FunctionalInterface
public interface Predicate {

    /**
     *  接收一个参数, 判断这个参数是否匹配某种规则, 匹配成功返回true, 匹配失败则返回false
     */
    boolean test(T t);

    /**
     *  接收一个 Predicate 类型的参数,用当前函数和 other 函数逻辑与判断参数 t 是否匹配规则,成功返回true,失败返回 false
     *  如果当前函数返回 false,则 other 函数不进行计算
     * 在评估 Predicate 期间引发的任何异常都会转发给调用方
     */
    default Predicate and(Predicate other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    /**
     *  返回当前Predicate取反操作之后的Predicate
     */
    default Predicate negate() {
        return (t) -> !test(t);
    }

    /**
     *  接收一个 Predicate 类型的参数,用当前函数和 other 函数 逻辑或 判断参数 t 是否匹配规则,成功返回true,失败返回 false
     *  如果当前函数返回 true,则 other 函数不进行计算
     * 在评估 Predicate 期间引发的任何异常都会转发给调用方
     */
    default Predicate or(Predicate other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    /**
     *  静态方法:传入一个参数,用来生成一个 Predicate,调用test() 方法时调的 object -> targetRef.equals(object) 函数式
     *
     */
    static  Predicate isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

相信大家在编码过程中经常会遇到该函数式接口,我们举个例子来说一下:

public static void main(String[] args) {
    List list = Arrays.asList(
        new Teacher("张三",25),
        new Teacher("李四",28),
        new Teacher("王五",18));

    list = list.stream().filter(item -> item.getAge()>25).collect(Collectors.toList());
    list.stream().forEach(item->System.out.println(item.getName()));
}

其中 filter() 的参数为 Predicate 类型的,返回结果为:李四

看到这儿,我们常见的四种函数式接口就已经介绍完了。说实话,函数式接口我已经看过好几遍了,尤其是 ConsumerSupplier。当时只是脑子里学会了,没有应用到具体的项目中,下次再遇到的时候还是一脸懵逼,不知道大家有没有这种感受。

所以我们需要总结经验教训,一定要将代码的原理搞懂,然后多敲几遍,争取应用到自己的项目中,提升自己的编码能力。

以上就是今天的全部内容了,如果你有不同的意见或者更好的 idea,欢迎联系阿Q,添加阿Q可以加入技术交流群参与讨论呦!

Original: https://www.cnblogs.com/aqsaycode/p/15601408.html
Author: 阿Q说代码
Title: 如果还不懂如何使用 Consumer 接口,来公司我当面给你讲!

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/685779/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • SGU 311. Ice-cream Tycoon(线段树)

    Time limit per test: 0.5 second(s)Memory limit: 65536 kilobytes input: standardoutput: sta…

    技术杂谈 2023年5月31日
    057
  • Vue 中关于页面全屏的样式定义

    vue开发中很需要页面全屏,尤其是实在登录页面的时候. 但是经常设置 height: 100%;不起作用. 原因分析: 给div设置高度100%时,div的高度会等同于其父元素的高…

    技术杂谈 2023年7月11日
    059
  • 六、IDEA安装

    一、IDEA下载与安装 1.1、下载IDEA安装包 博主在这里给大家准备了一个64位操作系统的IDEA以便大家下载(使用的是迅雷)点击此处下载提取码:dgiy 如果其他小伙伴的电脑…

    技术杂谈 2023年6月21日
    086
  • Spring Ioc源码分析系列–Ioc的基础知识准备

    Spring Ioc源码分析系列–Ioc的基础知识准备 本系列文章代码基于Spring Framework 5.2.x Ioc的概念 在Spring里,Ioc的定义为T…

    技术杂谈 2023年7月25日
    067
  • vim 个性化设置

    vim 编辑器安装yum install vim vim个性化设置vim ~/.vimrc复制以下代码 set nocompatible set number set ruler …

    技术杂谈 2023年7月11日
    060
  • 树莓派远程连接工具SSH使用教程

    树莓派远程连接工具SSH使用教程 树莓派 背景故事 树莓派作为一款迷你小主机,大部分的使用场景都会用到远程调试,远程调试用到最多的方式一般就是VNC和SSH,SSH就是命令行型的远…

    技术杂谈 2023年7月23日
    079
  • 自己动手写乞丐版线程池

    线程池的具体实现 在上一篇文章线程池的前世今生当中我们具体去介绍了线程池当中的原理。在线程池当中我们有很多个线程不断的从任务池(用户在使用线程池的时候不断的使用 execute方法…

    技术杂谈 2023年7月23日
    079
  • 网络多人游戏架构与编程1

    【 网络多人游戏架构与编程1】 1、即使在今天,大多数的多人在线游戏在 每个游戏会话中仍然限制玩家的数量 ,一般支持4~32个玩家。然而,在 大规模多人在线游戏(massive m…

    技术杂谈 2023年5月31日
    090
  • DVWA安装

    参考一下文章: (1条消息) DVWA安装教程(懂你的不懂·详细)_学会了再换名字的博客-CSDN博客_dvwa安装教程 另外对于文中的报红情况,在修改完php。ini文件后还要重…

    技术杂谈 2023年6月21日
    095
  • 分享最近做的一个中文 wordle 的游戏《词影》

    一款将象形汉字同 Wordle 猜测结合起来的猜成语小游戏 ——《词影》 目前还在内测中,欢迎大家试玩,还望各位拍砖 Orz 游戏关键词 类 wordle 象形文字 每天十局 体验…

    技术杂谈 2023年6月1日
    093
  • 浅析Kubernetes架构之workqueue

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    技术杂谈 2023年7月25日
    060
  • HTTPWeb安全

    验证安全机制 会话管理机制 SQL注入原理 SELECT * FROM test.user WHERE username=” or 1=’1′ and password=’any…

    技术杂谈 2023年7月24日
    067
  • strcpy和memcpy的差别

    char * strcpy ( char * dest, const char * src) // 实现src到d…

    技术杂谈 2023年5月31日
    063
  • mysql 8.0.28 查询语句执行顺序实测结果

    TL;NRs 根据实测结果,MySQL8.0.28 中 SQL 语句的执行顺序为: (7) SELECT (5) DISTINCT (1) FROM (3) JOIN (2/4) …

    技术杂谈 2023年7月25日
    070
  • Python基本语法学习

    CSN Python学习作业 Python的变量不需要声明,但每个变量在使用前都必须赋值。在Python中,变量就是变量,它没有所谓的”类型”一说 Pyth…

    技术杂谈 2023年7月11日
    062
  • html大文件分片传输

    我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用。 首先我们需要了解的是上传文件三要素: 1.表单提交方式:post (…

    技术杂谈 2023年5月30日
    077
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球