• jdk8 | Function<T,R>实践应用


    自动JDK8诞生,基于stream api的程序编码成为程序员秀代码的平台。事实上,除了stream相关api外,提供的lambda表达式,更新精简了不少代码。然而,对于大多数开发者而言,灵活运用基于函数式编程依然窃取,倘若灵活使用jdk8的相关函数式编程,可以有效精简我们的冗余代码,使得我们的代码可以更灵活的复用。这边,今天来谈谈JDK8中的Function具体该如何更灵活的运用,相信通过这篇文章,你有所收获。

    一、场景再现

    最近要完成一个小功能,该功能在一个工程中有类似业务逻辑实现,我了解了一下,顺便就可以快速实现这个功能模块开发。

    现有一个Business类中有如下一个方法

    public void syncVideoToDp(List<Long> ids) {
            int pageNo = 1;
            while (true) {
                try {
                    List<RecruitShortVideoBO> videoList = recruitShortVideoDAO.listAllLiveVideo(ids, pageNo);
                    if (CollectionUtils.isEmpty(videoList)) {
                        break;
                    }
                    sendUpdateUrlEvent(videoList);
                    pageNo = pageNo + 1;
                } catch (Exception e) {
                    logger.error("异常:" + e);
                    break;
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    上述方法,通过梳理逻辑,可以得出该方法的主要功能就是如下

    • 1、方法的参数是一个List类型,方法是void返回类型。
    • 2、内部有个循环,通过分页查询,委托给recruitShortVideoDAO查询数据集,如果数据集为空,则跳出循环;不为空,则吧查询的数据集进而调用成员方法sendUpdateUrlEvent

    而我呢,有个功能业务逻辑跟如上方法类似,区别在于接口的方法参数不一样,调用dao的另外一个方法,依然是分页查询,没有数据集则跳出循环。于是,实现逻辑复制如上代码然后最终实现如下:

    public void syncNearlyVideoToDp(Instant fromTime) {
            int pageNo = 1;
            while (true) {
                try {
                    List<RecruitShortVideoBO> videoList = recruitShortVideoDAO.pagingListNearlyByModifyTime(pageNo, fromTime);
                    if (CollectionUtils.isEmpty(videoList)) {
                        break;
                    }
                    sendUpdateUrlEvent(videoList);
                    pageNo = pageNo + 1;
                } catch (Exception e) {
                    logger.error("异常:" + e);
                    break;
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    二、优化思路

    我们通过对比如上两个方法syncVideoToDp(List ids) syncNearlyVideoToDp(Instant fromTime)可以发现整体代码业务逻辑很相似,但是呢代码很冗余,为什么呢,因为整体方法业务逻辑逻辑很相似。

    • 根据参数委托dao分页查询数据集,数据集为空,跳出while循环。
    • 数据集不为空,则委托成员方法sendUpdateUrlEvent处理数据。

    这时候,我们思考一下,无非区别在于查询的数据集不同,所以我们可以把代码优化如下。

    通过提取一个成员方法,不关注数据集,仅业务逻辑处理。

       /**
         * 分页循环执行处理
         * @param caller
         */
        void doHandlePagingLoop(Function<Integer,List<RecruitShortVideoBO>> caller){
            int pageNo = 1;
            while (true) {
                try {
                    List<RecruitShortVideoBO> videoList = caller.apply(pageNo);
                    if (CollectionUtils.isEmpty(videoList)) {
                        break;
                    }
                    sendUpdateUrlEvent(videoList);
                    pageNo = pageNo + 1;
                } catch (Exception e) {
                    logger.error("异常:" + e);
                    break;
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    上述这个方法,方法参数就是一个Function接口,该接口有个特点,入参是Integer类型,返回参数是List。因为每次循环的时候,pageNo+1,这时候,需要回调把pageNo传给调用方,查询下一页的数据集。

    方法一的代码优化如下:

    public void syncVideoToDp(List<Long> ids) {
            doHandlePagingLoop(pageNo -> recruitShortVideoDAO.listAllLiveVideo(ids, pageNo));
    }
    
    • 1
    • 2
    • 3

    方法二的代码优化如下:

    public void syncNearlyVideoToDp(Instant fromTime) {
            doHandlePagingLoop(pageNo -> {
                List<RecruitShortVideoBO> list = recruitShortVideoDAO.pagingListNearlyByModifyTime(pageNo, fromTime);
                logger.info("[syncNearlyVideoToDp],本次处理数据条数={},pageNo={}", list.size(), pageNo);
                return list;
            });
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    通过对比如上方法一和方法二,我们发现代码经过了精简优雅了许多。本质上而言,就是提取了相同的业务逻辑代码,使得该方法中doHandlePagingLoop不关注数据集是委托dao的某个方法,我只需要调用方给我数据集即可;其次呢,既然dao需要分页查询,新方法中则序号处理每页的数据后,pageNo+1,然后重新给调用法,查询下一页数据即可。

    三、扩展思考

    我们有没有发现jdk8中的stream api,本质上就是基于函数式编程,使得我么只需要传入一个lambda表达式即可。

    譬如 forEach 就是一个Consumer接口,譬如filter 就是个 Predicate接口,譬如map就是一个Function接口,我们通过查看java.util.stream的源码如下。

    /**
    * @since 1.8
    */
    public interface Stream<T> extends BaseStream<T, Stream<T>> {
      
      Stream<T> filter(Predicate<? super T> predicate);
      
      <R> Stream<R> map(Function<? super T, ? extends R> mapper);
      
      <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
      
      Stream<T> peek(Consumer<? super T> action);
      
      void forEach(Consumer<? super T> action);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    所以我们调用Stream的forEach方法,本身就是把迭代的元素返回给调用方,所以源码中Consumer 就是具体的泛型 ,如果我循环的是集合List 那么得到的元素也就是 Student;如果我们调用的map方法,得到的回调元素也是Student,这就是lambda(函数式)的魅力。

  • 相关阅读:
    排序。。。。
    yarn节点属性及调度
    Unity性能优化一本通
    P1151 子数整数
    【# 软件stm32cubeIDE下使用STM32F103的ADC+DMA测量-基础样例+进阶+实例应用>>热敏电阻温度测量】
    批次管理在MES管理系统中有哪些应用
    excel查找与引用函数
    C. Even Picture(构造)
    karmada介绍和分析
    uniapp H5的弹窗滚动穿透解决
  • 原文地址:https://blog.csdn.net/shichen2010/article/details/126407964