• JDK8新特性--函数式接口--(Consumer的概念理解,模拟练习,企业实战)全流程彻底搞懂


    背景,起因是因为在项目开发过程中,发现了一处代码的写法觉得很新奇看不懂,了解后发现是用到了函数式接口的知识。特此学习记录,整体过程梳理在本文。如果你不满足就会写个CURD,业务代码只会new来new去,代码维护性差,本文可以给你提供思路,告诉你写出一个巨屌的代码,同样的逻辑,不同的写法,其他程序员一眼看的哇塞和懵逼。

    1 本人对【函数式接口】的理解

    • 为什么以前没听说过这个东西?

    答:JDK8新特性,Java提出了函数式接口。

    • 为什么要使用函数式接口?

    使用函数式接口,提高了代码的灵活性,后面的案例会带你体验有多吊,吊打你以前的写法

    • 什么是函数式接口?

    接下来会从两个维度解释。

    维度一:资料定义
    在这里插入图片描述

    希望第一步你能够看懂这个解释,能看懂 【匿名内部类】和【lambda表达式】写法
    若果这一步吃力的话,可以参考这篇文章,写的很基础和全面,通俗易懂
    点击查看-> 匿名内部类、Lambda、方法引用 的总结


    维度二:JDK提供的函数式接口

    对于新手的我们,使用函数式接口,基本上就是使用JDK提供的大量函数式接口。不同场景下使用不同的函数式接口,现在还没能力去自定义一个函数式接口,让项目代码有优雅。

    在搜索资料的过程中,了解到了一个新名词:Java语法糖
    (为了不影响本文梳理逻辑,关于语法糖的介绍我放到了章节,右侧目录可以查看)

    这里作者想要强调的是,使用JDK提供的函数式接口,让我们代码更优雅了,某种程度上符合
    Java语法糖的思想。理解这一点,也就能更透彻的理解函数式接口这个设计的优点。

    好了,回到正题

    • JDK提供了哪些函数式接口呢?
      在这里插入图片描述
      在这里插入图片描述
      本文介绍的是java.util.function包下的接口,其中核心的有
    • Supplier接口
    • Consumer接口
    • Predicate接口
    • Function接口
    • 等等

    下面会介绍 Consumer接口使用案例

    2 函数式接口–>Consumer接口案例

    现在有一个需求:创建一个list列表,存储数据,直接打印列表的内容

    @Test
    public void demo(){
    	List<String> list = Arrays.asList("John","Andy");
    	printList(list);
    }
    
    public void printList(List<String> list){
    	for(String s : list){
    		System.out.println(s);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    现在我们的代码很优雅,客户需要打印功能,只需要调用printList方法就行,完全不需要操心如何实现的

    现在客户需求变了:创建一个list列表,存储数据,将大写转化为小写后再打印列表的内容

    @Test
    public void demo(){
    	List<String> list = Arrays.asList("John","Andy");
    	printList(list);
    }
    
    public void printList(List<String> list){
    	for(String s : list){
    		//变动的地方
    		System.out.println(s.toLowerCase);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    按照客户需求,只需要修改一次printList方法的内容就行了。完美解决

    现在需求又又变了:创建一个list列表,存储数据,将小写转化为大写后再打印列表的内容

    @Test
    public void demo(){
    	List<String> list = Arrays.asList("John","Andy");
    	printList(list);
    }
    
    public void printList(List<String> list){
    	for(String s : list){
    		//变动的地方
    		System.out.println(s.toUpperCase);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    再次按照客户需求,第二次修改printList方法的内容。完美解决

    现在需求又又又又又变了…,是不是每次变动,都需要修改printList方法内容,这也太麻烦了。我打算把控制权给客户,需要什么自己去修改,不要动我 printList()方法的代码了。

    如何实现呢,借用函数式接口Consumer

    @Test
    public void demo(){
    	List<String> list = Arrays.asList("John","Andy");
    	printList(list, new Consumer<String>() {
    		@Override
    		public void accpet(String s){
    			System.out.println(s);
    			//System.out.println(s.toLowerCase);
    			//System.out.println(s.toUpperCase);
    		}
    	}
    }
    
    public void printList(List<String> list,Consumer<String> consumer){
    	for(String s : list){
    		consumer.accept(s);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这里使用了匿名内部类的写法,实际上不这样写,而是使用lambda表达式,优化后的代码

    @Test
    public void demo(){
    	List<String> list = Arrays.asList("John","Andy");
    	printList(list, s-> System.out.println(s));
    	}
    }
    
    public void printList(List<String> list,Consumer<String> consumer){
    	for(String s : list){
    		consumer.accept(s);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    分析一下,当程序运行后,开始调用printList方法,进入printList方法后,执行for循环,每次遍历都要执行accept方法,accept方法被重写了,就执行重写的内容。

    当然了,看到这里你肯定会心有疑惑:我就是想要遍历+打印而已,怎么弄得这么复杂,还不如直接全写在demo()里面,使用最原始的方式。

    这么想就错了,企业的项目是很复杂的,不是本案例演示的这些功能。

    3 项目使用的案例

    我现在得到一个json格式的字符串,需要将其解析后存到list中,得到list的长度,如果长度大于2,打印:我是2,否则打印:我是其他

    3.1 普通写法

    文件A
    public class JsonUtils {
        public int parseJsonString(String jsonString) {
            JSONArray jsonArray = JSON.parseArray(jsonString);
            List<Person> personList = jsonArray.toJavaList(Person.class);
            int length = personList.size();
            return length;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    文件B
    public class Example {
        public static void main(String[] args) {
            String jsonString = "[{\"name\":\"Alice\", \"age\":25}, {\"name\":\"Bob\", \"age\":30}]";
    		
            int length = JsonUtils.parseJsonString(jsonString);
    		
    		printResult(length);
        }
        
    	private static void printResult(int length) {
            if (length > 2) {
                System.out.println("我是2");
            } else {
                System.out.println("我是0");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这种普通写法是我们大家最习惯的写法,阅读起来完全没有问题。
    注意分析主函数:我们一共传递了两次参数
    在这里插入图片描述

    那么接下来我们尝试修改为函数式接口的写法

    3.2 lambda+函数式接口写法

    看不懂代码,先看本章节后面结论

    文件A
    public class JsonUtils {
        public static void parseJsonString(String jsonString, Consumer<Integer> consumer) {
            JSONArray jsonArray = JSON.parseArray(jsonString);
            List<Person> personList = jsonArray.toJavaList(Person.class);
            int length = personList.size();
            consumer.accept(length);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    文件B
    
    public class Example {
        public static void main(String[] args) {
            String jsonString = "[{\"name\":\"Alice\", \"age\":25}, {\"name\":\"Bob\", \"age\":30}]";
    
            Consumer<Integer> consumer = length -> {
                if (length > 2) {
                    System.out.println("我是2");
                } else {
                    System.out.println("我是其他");
                }
            };
    
    		//将该Lambda表达式传递给JsonUtils类的parseJsonString方法,以执行解析JSON字符串、获取列表长度并打印结果信息的操作。
            JsonUtils.parseJsonString(jsonString, consumer);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    看看能否理解此代码?
    在这里插入图片描述
    可以看到此部分的代码使用了函数式接口+lambda表达式,希望大家先知道如何写这种风格的代码,作者知道大家一定一定会非常疑惑,改造后的代码怎么感觉更乱了,一点没看出来哪里优雅,总不能说使用了函数式接口就优雅。
    注意看,粗看感觉很乱,仔细分析主函数内容:
    在这里插入图片描述

    可以发现,我们这次只传递了一次参数。只不过lambda表达式那一坨看着很恶心,不美观。要是能抽离出去,成为一个单独的方法就好了

    3.3 方法引用+函数式接口写法

    A 文件
    
    public class JsonUtils {
        public static void parseJsonString(String jsonString, Consumer<Integer> consumer) {
            JSONArray jsonArray = JSON.parseArray(jsonString);
            List<Person> personList = jsonArray.toJavaList(Person.class);
            int length = personList.size();
            consumer.accept(length);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    B 文件
    
    public class Example {
        public static void main(String[] args) {
            String jsonString = "[{\"name\":\"Alice\", \"age\":25}, {\"name\":\"Bob\", \"age\":30}]";
    
            Consumer<Integer> consumer = Example::printResult;
    
            JsonUtils.parseJsonString(jsonString, consumer);
        }
    
        private static void printResult(int length) {
            if (length > 2) {
                System.out.println("我是2");
            } else {
                System.out.println("我是其他");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这种写法简直太完美:传递一次参数,代码简洁。

    4 总结

    总结一下:

    在这里插入图片描述
    这里作者可以再具体说说有什么好处。

    原客户需求:我现在得到一个json格式的字符串,需要将其解析后存到list中,得到list的长度,如果长度大于2,打印:我是2,否则打印:我是其他

    现在客户需求变了,且需要你的同事负责开发:

    • 我现在得到一个json格式的字符串,需要将其解析后存到list中,得到list的长度,

    • 如果长度小于2,打印:我比2小;

    • 长度等于2,打印:我是2;

    • 长度大于2,打印:我比2大

    如果你按照最初代码版本写的
    你的同事:需求修改printResult()方法内容,然后在主函数进行测试:调用parseJsonString()方法获取返回值,将返回值传递给printResult()方法

    如果你按照最初代码版本写的
    你的同事:需求修改printResult()方法内容,然后在主函数进行测试:不需要改动代码

    神奇吧,神奇吧,就是这么玩的。第一次写完后,以后的程序员不需要关心调用了什么方法,传递了什么参数,他需要做的仅仅就是修改业务代码printResult()。这代码质量太高了。拓展性,封装性,维护性都考虑到了。

    5 我的企业代码实战案例

    文件A.java
    public class SyslogDataSyncConsumer<K, V> extends AbstractKafkaConsumer<K, V> {
    @Override
        public void afterPropertiesSet() throws Exception {
            
            	// SyslogDataSyncConsumer这是自定义的方法,不必在意
              SyslogDataSyncConsumer<String, String> consumer = new SyslogDataSyncConsumer<String, String>;
              
              Integer syncCount = consumer.consumerInfo(
                               	Collections.singletonList(KafkaTopicConst.Event_BMS_SYSLOG_ROLE),
                                consumer::handle);
          }
    
         public void handle(ConsumerRecords<String, String> records) {........(业务功能处理)    }      
          
          }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    文件B.java
    
    public Integer consumerInfo(List<String> topics,
                                    Consumer<ConsumerRecords<K, V>> recordsHandler)
      {
            // 拉取超时毫秒数,如果没有新的消息可供拉取,consumer会等待指定的毫秒数,到达超时时间后会直接返回一个空的结果集
            ConsumerRecords<K, V> records = null;
            
      		....
    		//省略详细处理recods过程
    		....
    		
            //kafka消息处理器处理读取到的消息
            recordsHandler.accept(records);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    能看懂此代码吗?
    内部的原理就是图示的流程
    这里解释一下,

    5 语法糖

    在这里插入图片描述
    通俗点解释,我们在写代码的时候,需要尽量争取让我们的代码更优雅,更容易理解。
    比如正常遍历数组arrayTest

    for(int i = 0; i < arrayTest.seize(); i++){
    	Sysytem.out.println(arrayTest[i]);
    }
    
    • 1
    • 2
    • 3

    JDK1.5版本后,支持使用增强for循环写法

    for (String s : arrayTest) {
        Sysytem.out.println(s);     
    }
    
    • 1
    • 2
    • 3

    可以看到这种效果是不是更优雅,更不容易出错(不用担心循环次数,数组下标是否正确)

    还有哪些地方用到了语法糖,参考本文链接---->[点击查看]

    10 JDK8的新特性有哪些

    点击查看–> JDK8的新特性参考文章

  • 相关阅读:
    程序员如何进化成架构师?
    敏捷开发笔记(第10章节)--Liskov原则(LSP)
    Python代码写好了怎么运行
    域名解析中的A记录和CNAME什么意思
    使用CMake构建一个简单的C++项目
    [蓝桥杯 2022 省 A] 推导部分和
    【JavaSE专栏53】Java集合类HashMap详解
    怎么他们都有开源项目经历|手把手教你参与开源
    TSINGSEE青犀视频平台Linux云存储挂载工具使最新配置与部署方式
    c++学习---第四部分下
  • 原文地址:https://blog.csdn.net/heng000000/article/details/132823508