将泛型和函数式编程结合,竟然会让代码这么优雅!

news2024/11/13 19:26:26

但这种方式却太表象了,没有灵魂和深度,过去的那些日子,我感觉自己的编程水平也就限于把重复的代码抽一抽,(如下图所示一样),甚至觉得代码优化不就是这样吗,这样的状态一直维持很久。

图片

image.png

然后后来让我感受到这种优雅艺术的点,正是泛型和函数式编程!

2.1 结构化的代码

以分页为例子,来感受一下什么是结构化的代码。特别说明一下:

  • 分页还需当前页数、页大小,以及校验等,本案例忽略;

  • 代码主要逻辑:查询分页条数,如果为 0 ,则不用查询列表详情,直接返回;如果分页条数大于 0 则查询列表详情。

代码一、返回总数和分页详情,查询 Book 表:

public PageData queryBook(BookRequest request) {
     // 1. 创建分页对象
    PageData pageData = new PageData();

    // 2. 计算满足的记录数
    int count = bookMapper.queryBookCount(request); 
  
    // 3. 为 0,则表示没有符合的数据,直接返回
    if (count == 0) {
        pageData.setCount(0);
        return pageData;
    }    
    // 4. 不为 0,计算记录详情
    List<BookDO> bookList = bookMapper.queryBookList(request);
    
    // 5. 封装记录总数和
    pageData.setCount(count);
    pageData.setResult(bookList);
    
    return pageData;
}

代码二、返回总数和分页详情,查询 Pencil 表:

public PageData queryPencil(PencilRequest request) {
     // 1. 创建分页对象
    PageData pageData = new PageData();

    // 2. 计算满足的记录数
    int count = pencilMapper.queryPencilCount(request); 
  
    // 3. 为 0,则表示没有符合的数据,直接返回
    if (count == 0) {
        pageData.setCount(0);
        return pageData;
    }    
    // 4. 不为 0,计算记录详情
    List<PencilDO> pencilList = pencilMapper.queryPencilList(request);
    
    // 5. 封装记录总数和
    pageData.setCount(count);
    pageData.setResult(pencilList);
    
    return pageData;
}

将两段代码进行对比:

bookpencil

图片

图片

得出结论:

  • 结构相似:外形

  • 语义相似:分页语义一致,先查询 count,然后再根据 count 是否查询 List 详情

如果再有其他实体对象的分页,那么 CV 一下,改改我上面的红框的地方即可。

基于上面这个案例,我们再深度思考一下。

2.2 重复代码的考量

在系统里面,这样结构化的代码随处可见。那么这两个方法代码有重复代码吗?

好像并没有(IDEA没有提示),因为很难找到大块重复的代码;也很难抽取出来一个具体的方法,然后被调用!

就像下面图中展示的那样,选中重复代码,然后抽取一个新的方法,对重复代码进行替换!下面的这种操作就是我早期进行代码重构的核心技能!

图片

虽然很难找到大块重复代码,但是上面的代码从 “外形骨架” 上看,却极其相似,难道这种相似不应该也是一种重复吗?

这种结构化式的重复,曾经困扰了我很久,我很难像抽取重复代码一样去抽取这种相似的结构!

遇到这样结构化的代码,我也不得不加入 CV 大军;并自我PUA,这样的代码并不是重复的代码!!!

随着对代码的思考和深入,一种独特的组合,彻底解决这种结构化的重复,那便是泛型+函数式编程

📑三、泛型 + 函数式编程

我认为它们的组合是天生解决这种结构化的!

3.1 泛型特性

泛型,"一切皆行",泛型在于它的普适性、通用性。太多工具类都使用了泛型!当然我也喜欢用泛型,因为它很优雅!

下面是一个代码片段。对请求返回结果进行包装;特别适用于 RPC 远程调用结果等场景。针对不同的返回实体,可以使用 T 类型 来表示。非常通用!

  • T 代表一切实体

  • success 是否请求成功

  • errorCode、errorMsg 出错时提供错误码和错误消息

public class ServiceResult<T> {
    
    /**
     * 请求是否成
     */ 
    private Boolean success;
    
    /**
     * 错误码
     */
    private String errorCode ;

    /**
     * 错误信息
     */
    private String errorMsg;
    
    /**
     * 返回内容
     */
    private T content;
    
    ......
    
}

除了上面这个案例以外,在很多工具类库中都十分常见。例如:下面是 hutool 包中的一个工具类。

  • 一个 set 集合如果为 null,则创建一个空的集合对象,否则返回原来的集合对象

图片

image.png

泛型为简化重复代码而生!

泛型的适用的场景太多,比如下面场景:

  • 工具类中使用

  • 抽象类;模板方法,构建标准步骤中使用

  • 顶层接口类中使用

  • 甚至使用 Object 的场景都可以考虑使用泛化来替代

......

接下来再聊聊从 JDK8 开始的新特性(语法糖),函数式编程。

3.2 函数式编程

在函数式编程中,可将方法作为参数进行传递调用;灵活性不言而喻

下面这几行代码是基于 guava 的 ListenableFuture 封装的一个异步回调。Callable 可以代表所有的方法。(匿名内部类)

public static <V> ListenableFuture<V> invokeWithFuture(
       Callable<V> callable) {
    return gPool.submit(callable);
}

使用如下:

@Test
public void invokeWithFuture() throws Execution {

    ListenableFuture<String> result = 
              AsyncInvoke.invokeWithFuture(() -> "hello");

    System.out.println(result.get());
}

() -> "hello" 作为一个代码块被传入到了方法中。是的,将代码块作为参数传递!

函数式编程的好处,让代码变得如此的灵活。困扰我多年的问题终于有了解法了。

📜四、华丽转身

  • 泛型:解决通用性

  • 函数式编程:将代码块用函数作为参数进行传递

于是基于分页的结构化问题,使用 泛型 + 函数式编程 进行解决!

  • 分页总数,使用 countFunction 计算

  • 分页详情,使用 listFunction 获取

如下所示:

@SneakyThrows
public static <T> PageData buildPageData(
            Callable<Integer> countFunction, 
            Callable<List<T>> listFunction) {
    // 1. 创建分页对象
    PageData pageData = new PageData();

    // 2. 计算满足的记录数
    int count = countFunction.call();

    // 3. 为 0,则表示没有符合的数据,直接返回
    if (count == 0) {
        pageData.setCount(0);
        return pageData;
    }
    // 4. 不为 0,计算记录详情
    List<T> resultList = listFunction.call();


    // 5. 封装记录总数和
    pageData.setCount(count);
    pageData.setResult(resultList);

    return pageData;
}

补充分页对象代码:

@Data
static class PageData<T> {
    private int count;
    private T result;
}

使用情况:

@Test
public void testBuildPageData(String[] args) {
    buildPageData(()-> 1, () -> Arrays.asList("1"));
}
  • 第一个参数是求记录数的方法

  • 第二个参数是求详情的方法

从那时起,结构化的代码,我不再进行 CV 了。泛型和函数式的编程让我的代码重复率又下降一个水位!

小秘诀:将变动的部分以函数方式进行变量替换;从而保留骨架,达到泛化和通用。就像下面这张图一样,“骨肉分离”,肉是细节代码;骨是结构框架。

图片

image.png

📚五、更多案例

我开始大量实践后,这样的代码也越来越多。接着再分析一个详细的案例。

下面是一个异步回调的工具类。

首先,定义异步回调的任务接口,所有目标对象需要实现该接口才能作为异步回调的参数进行调用

public interface CallbackTask<R> {
    R execute();
    default void onSuccess(R r) {
    }
    default void onFailure(Throwable t) {
    }
}

方法理解:

  • execute 主方法,目标任务需要具体实现

  • 方法执行成功后回调 onSuccess 方法

  • 方法执行失败后回调 onFailure 方法

下面是具体实现:通过 CompletableFuture 来实现异步回调!调用执行链路逻辑:

  • supplyAsync 异步执行任务

  • whenComplete 异步执行结束回调,不管失败成功都会调用,因此我做了一下判断

  • exceptionally 失败场景回调

/**
 * 借助 CompletableFuture 来实现异步行为。
 * 不会抛出异常,在 onFailure 中处理异常
 *
 * @param executeTask
 * @param <R>
 * @return
 */
private static <R> CompletableFuture<R> doInvoker(
            CallbackTask<R> executeTask) {
    CompletableFuture<R> invoke = CompletableFuture
            .supplyAsync(() -> {
                try {
                    return executeTask.execute();
                } catch (Exception exception) {
                    throw new BizException(
                        ASYNC_INVOKER_ERROR.getErrorCode(), 
                        exception.getMessage());
                }
            }, gPool)
            .whenComplete((result, throwable) -> {
                // 不管成功与失败,whenComplete 都会执行,
                // 通过 throwable == null 跳过执行
                if (throwable == null) {
                    executeTask.onSuccess(result);
                }
            })
            .exceptionally(throwable -> {
                executeTask.onFailure(throwable);
                // todo 给一个默认值,或者使用 Optional包装一下,否者异常会出现NPE
                return null;
            });
    return invoke;
}

上面代码是整个骨架, 实现了异步回调。

下面是具体使用:

CompletableFuture<Integer> result = 
    AsyncInvoke.doInvoker(new CallbackTask<Integer>() {      
        public Integer execute()  {
            int result = 1 + 1;
            return result;
        }

        @Override
        public void onSuccess(Integer integer) {
            System.out.println("on success result: " + integer);
        }

        @Override
        public void onFailure(Throwable t) {
            System.out.println("error " + t.getMessage());
        }
});
  • result#get 可以获取异步结果

  • 执行成功后调用 onSuccess,失败会调用 onFailure

还有很多其他场景都可以使用  泛型 + 函数式编程 来解决

  • 针对每个方法限流

  • 针对每个方法重试

......

它解决了重复,让代码看起来优雅!

虽然如此,但这样的组合,也会带来一些不足。

📝六、不足之处

  • 泛型和函数式编程不方便调试;出问题的时候比较显著

  • 理解有成本,尤其方法不熟悉的时候

  • 同时降低一定的可读性

🔊七、最后感想

泛型和函数式编程只是 Java 中的语法糖,它算不上编程的内功心法,只是一种展现形式而已。我们更多应该关注的是如何对一系列具体的场景进行抽象,然后再通过工具去实现它们。就像如何去定义一个泛型,如何去抽象一个函数一样。

最后说一句(求关注!别白嫖!)

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!

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

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

相关文章

Linux多线程——利用C++模板对pthread线程库封装

文章目录 线程封装主要框架线程启动线程等待其他信息 测试函数 线程封装 我们之前介绍过pthread的线程库&#xff0c;这个线程库主要是基于C语言的void*指针来进行传参和返回 我们使用C的模板对其封装可以让他的使用更加方便&#xff0c;并且经过测试可以让我们更加直观的了解…

DPDK基础入门(二):Cache与大页优化

Cache简介 目前Cache主要由三级组成: L1 Cache, L2 Cache和Last Level Cache(LLC)。 L1最快&#xff0c;但容量小&#xff0c;可能只有几十KB。LLC慢&#xff0c;但容量大&#xff0c;可能多达几十MB。 L1和L2 Cache一般集成在CPU内部。另外,&#xff0c;L1和L2 Cache是每个处…

【2024】Datawhale X 李宏毅苹果书 AI夏令营 Task3

本文是关于李宏毅苹果书”第2章 实践方法论“学习内容的记录。 模型在测试集上表现不佳&#xff0c;可能是因为模型没有充分学习训练集。模型不能充分学习训练集的原因&#xff1a; 模型偏差优化问题过拟合不匹配 一、模型偏差 模型偏差是指&#xff1a;由于模型过于简单&a…

网站如何针对不同的DDOS进行防御?

建设网站租用服务器是多数企业及个人的选择&#xff0c;一个安全稳定的服务器对网站的重要性无需再赘述。要保证服务器租用的安全和稳定&#xff0c;除了需要服务器自身有强大的硬、软件基础之外&#xff0c;还需要防范外部的一些因素&#xff0c;常见的就是各种网络攻击&#…

Linux 上如何做MySQL数据备份

目录 SQL备份脚本创建crontabcrontab命令总结查看特定目录中的周期性任务 crontab&#xff08;cron table 的缩写&#xff09;是 Unix/Linux 系统上用于设置周期性被执行的任务的工具。它允许用户定义需要在特定时间&#xff08;比如每天凌晨、每周的某个时间等&#xff09;自动…

驭势科技研究成果入选学术顶会IROS 2024

近日&#xff0c;驭势科技团队关于自动驾驶车辆定位算法的最新研究成果《LiDAR-based HD Map Localization using Semantic Generalized ICP with Road Marking Detection》&#xff0c;创造性地解决了基于LiDAR的实时路标检测和高精地图配准所带来的挑战&#xff0c;成功入选国…

汇川技术|KingIOServer与AC810PLC通过ModbusTCP通讯测试

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 最近有个项目用亚控的KingSCADA软件开发SCADA系统&#xff0c;需要和汇川的AC810PLC进行通讯&#xff1b; 本节测试亚控的采集软件KingIOServer与汇川的AC810PLC的通讯测试。 以下为测试笔记。 01 效果演示 测试过程…

3个苹果锁屏密码解锁方法,帮你快速解决密码忘记的烦恼!

苹果手机锁屏密码忘记了是一件很常见的问题&#xff0c;但也是一件让人头疼的事情。如果你遇到了这样的问题&#xff0c;不要着急&#xff0c;因为有很多方法可以帮助你解锁iPhone。下面我们将介绍四种简单的方法来解锁iPhone。 一、使用密码解锁工具 iphone忘记了密码怎么解锁…

iconfont图标字体库详细介绍

概述 图标库在前端开发中应用十分广泛&#xff0c;图标库不仅会丰富美化界面的展示&#xff0c;语义化的图标库更能简洁明了地向用户传达某些信息&#xff0c;比如功能的特性和作用&#xff0c;引导用户&#xff0c;极大提高系统的易用性。在没有 UI 设计师的情况下&#xff0…

【C++】手动实现队列的封装(C++)

目录 源代码&#xff1a; 输出结果如下&#xff1a; 实现以下封装 源代码&#xff1a; #include <iostream>using namespace std;class Queue { private:int* arr; // 队列的动态数组int front; // 队列头部元素的索引int rear; // 队列尾部元素的索引in…

新版某数字壳脱壳,过frida检测,及重打包

目录 脱壳 寻找特征& frida hook 过frida检测 修复dex 重打包 修改smail 去签名校验 正文 大家好&#xff0c;我是小生&#xff0c;这次的app是一个国内某计划app, 功能相当全&#xff0c;界面也很美观&#xff0c;很实用&#xff0c;这个app我很欣赏。总共花了有…

【SQL】Delete使用

目录 语法 需求 示例 分析 代码 语法 DELETE删除表中所需内容 删除表中满足特点条件的行&#xff1a;DELETE FROM 表名 WHERE 条件; 删除表中所有行&#xff1a;DELETE FROM 表名; WHERE子句 WHERE子句用于指定从表中选取记录的条件。允许筛选数据&#xff0c;只返回满足…

【文献精读】基于驱动力表的无人车终端无约束预测纵向控制(TVT)

写在前面&#xff1a; &#x1f31f; 欢迎光临 清流君 的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落。&#x1f4dd; 个人主页&#xff1a;清流君_CSDN博客&#xff0c;期待与您一同探索 移动机器人 领域的无限可能。 &#x1f50d; 本文系 清流君 原创之作&…

ElasticSearch学习笔记(六)自动补全、拼音分词器、RabbitMQ实现数据同步

文章目录 前言11 自动补全11.1 拼音分词器11.2 自定义分词器11.3 自动补全查询 12 数据同步12.1 实现方案12.1.1 同步调用12.1.2 异步通知12.1.3 监听binlog 12.2 异步通知实现数据同步12.2.1 声明交换机和队列12.2.2 发送MQ消息12.2.3 接收MQ消息并操作ES 前言 ElasticSearch…

数据结构————单向链表

头插&#xff1a; 尾插&#xff1a; 头删&#xff1a; 尾删&#xff1a;

一种常用嵌入式开发代码库

链接&#xff1a;https://gitee.com/zhangxinyuanqi/varch 使用开源协议&#xff1a;GPL-2.0 varch简介 varch&#xff08;we-architecture&#xff0c;意为我们的框架库&#xff09;是嵌入式C语言常用代码模块库&#xff0c;包含了嵌入式中常用的算法库, 数据结构&#xff…

JPA关联MyBatis

3.1 JPA 多表查询 多表查询在 Spring Data JPA 中有两种实现方式&#xff0c;第一种是创建一个结果集的接口来接受多表连接查询后的结果&#xff0c;第二种是利用 JPA 的关联映射来实现 3.1.1 数据库表及关系 CRM 数据库中除 sys_user(用户)表外&#xff0c;还包括sys_role(角…

触想内嵌式工业一体机应用于智能检票机改善旅游体验

一、行业发展背景 每年下半年&#xff0c;暑假、中秋、国庆总是接踵而至&#xff0c;随之而来的出游高峰一波接一波。凶猛需求之下&#xff0c;各地景区、游乐园客流压力加大&#xff0c;特别在检票环节&#xff0c;人工检票效率低、秩序混乱&#xff0c;导致常常出现检票口人山…

POL(Point-of-Load)负载点电源

负载点&#xff08;POL&#xff09;电源在靠近负载处单独放置电源调节器(线性稳压器或DC-DC)&#xff0c;解决了高性能半导体器件&#xff0c;例如&#xff1a;微控制器、ASIC等&#xff0c;所面临的高峰值电流、低噪声裕量等设计挑战。 一般我们会把负载点电源尽量靠近负载放…

乾元通多卡聚合技术在无人配送车应用领域通信保障方案

在无人驾驶公交车、安防车、售卖车、清扫车相继亮相后&#xff0c;无人配送车在全国各地也陆续“上岗”&#xff0c;为我们的城市带来了与众不同的“智慧体验”&#xff0c;让城市有了“科技温度”。 无人配送车在营业部装载好快递后&#xff0c;会按照提前规划好的路线出发&a…