范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

用好JAVA中的函数式接口,轻松从通用代码框架中剥离业务定制逻辑

  大家好,又见面了。
  今天我们一起聊一聊JAVA中的 函数式接口 。那我们首先要知道啥是函数式接口、它和JAVA中普通的接口有啥区别?其实函数式接口也是一个Interface  类,是一种比较特殊的接口类,这个接口类有且仅有一个抽象方法(但是可以有其余的方法,比如default  方法)。
  当然,我们看源码的时候,会发现JDK中提供的函数式接口,都会携带一个  @FunctionalFunction  注解,这个注释是用于标记 此接口类是一个函数式接口,但是这个注解并非是实现函数式接口的必须项 。说白了,加了这个注解,一方面可以方便代码的理解 ,告知这个代码是按照函数式接口来定义实现的,另一方面也是供编译器协助检查 ,如果此方法不符合函数式接口的要求,直接编译失败,方便程序员介入处理。
  所以归纳下来,一个函数式接口应该具备如下特性: 是一个JAVA interface类 有且仅有1个公共抽象方法 有 @FunctionalFunction  标注(可选)
  比如我们在多线程场景中都很熟悉的 Runnable  接口,就是个典型的函数式接口,符合上面说的2个特性:@FunctionalInterface public interface Runnable {     /**      * When an object implementing interface Runnable is used      * to create a thread, starting the thread causes the object"s      * run method to be called in that separately executing      * thread.      *
  * The general contract of the method run is that it may      * take any action whatsoever.      *      * @see     java.lang.Thread#run()      */     public abstract void run(); } 
  但是,我们在看JDK源码的时候,也会看到有些函数式接口里面有 多个  抽象方法。比如JDK中的 Comparator  接口的定义如下:@FunctionalInterface public interface Comparator {     int compare(T o1, T o2);     boolean equals(Object obj);     // 其他方法省略... }
  可以看到, Comparator 接口里面提供了 compare  和 equals  两个抽象方法。这是啥原因呢?回答这个问题前,我们可以先来做个试验。
  我们自己定义一个函数式接口,里面提供两个抽象方法测试一下,会发现IDEA中直接就提示编译失败了:
  同样是这个自定义的函数式接口,我们修改下里面的抽象方法名称,改为  equals  方法,会发现这样就不报错了:
  在IDEA中可能更容易看出端倪来,在上面的图中,注意到12行代码前面那个  @  符号了吗?我们换种写法,改为如下的方式,原因就更加清晰了:
  原来,这个  equals  方法,其实是继承自父类的方法,因为所有的类最终都是继承自Object类 ,所以 equals  方法只能算是对父类接口的一个覆写,而不算是此接口类自己的抽象方法,所以此方法里面实际上还是只有 1个  抽象方法,并没有违背函数式接口的约束条件。
  函数式接口在JDK中的大放异彩
  JDK源码  java.util.function  包下面提供的一系列的预置的函数式接口定义:
  部分使用场景比较多的函数式接口的功能描述归纳如下:
  接口类
  功能描述
  Runnable
  直接执行一段处理函数,无任何输出参数,也没有任何输出结果。
  Supplier
  执行一段处理函数,无任务输入参数,返回一个T类型的结果。与Runnable的区别在于Supplier执行完之后有返回值。
  Consumer
  执行一段处理函数,支持传入一个T类型的参数,执行完没有任何返回值。
  BiConsumer
  与Consumer类型相似,区别点在于BiConsumer支持传入两个不同类型的参数,执行完成之后依旧没有任何返回值。
  Function
  执行一段处理函数,支持传入一个T类型的参数,执行完成之后,返回一个R类型的结果。与Consumer的区别点就在于Function执行完成之后有输出值。
  BiFunction
  与Function相似,区别点在于BiFunction可以传入两个不同类型的参数,执行之后可以返回一个结果。与BiConsumer也很类似,区别点在于BiFunction可以有返回值。
  UnaryOperator
  传入一个参数对象T,允许对此参数进行处理,处理完成后返回同样类型的结果对象T。继承Function接口实现,输入输出对象的类型相同。
  BinaryOperator
  允许传入2个相同类型的参数,可以对参数进行处理,最后返回一个仍是相同类型的结果T。继承BiFunction接口实现,两个输入参数以及最终输出结果的对象类型都相同。
  Predicate
  支持传入一个T类型的参数,执行一段处理函数,最后返回一个布尔类型的结果。
  BiPredicate
  支持传入2个相同类型T的参数,执行一段处理函数,最后返回一个布尔类型的结果。
  JDK中  java.util.function   包内预置了这么多的函数式接口,很多场景下其实都是给JDK中其它的类或者方法中使用的,最典型的就是Stream  了——可以说有一大半预置的函数式接口类,都是为适配Stream相关能力而提供的。也正是基于函数式接口的配合使用,才是使得Stream的灵活性与扩展性尤其的突出。
  下面我们一起来看几个Stream的方法实现源码,来感受下函数式接口使用的魅力。
  比如,Stream中的  filter  过滤操作,其实就是传入一个元素对象,然后经过一系列的处理与判断逻辑,最后需要给定一个boolean 的结果,告知filter操作是应该保留还是丢弃此元素,所以filter方法传入的参数就是一个 Predicate  函数式接口的具体实现(因为Predicate接口的特点就是传入一个T对象,输出一个boolean结果):/** * Returns a stream consisting of the elements of this stream that match * the given predicate. */     Stream filter(Predicate<? super T> predicate);
  又比如,Stream中的  map  操作,是通过遍历的方式,将元素逐个传入函数中进行处理,并支持输出为一个新的类型对象结果,所以map方法要求传入一个 Function  函数式接口的具体实现:/**  * Returns a stream consisting of the results of applying the given  * function to the elements of this stream.  */  Stream map(Function<? super T, ? extends R> mapper);
  再比如,Stream中的终止操作  forEach  方法,其实就是通过迭代的方式去对元素进行逐个处理,最终其并没有任何返回值生成,所以forEach方法定义的时候,要求传入的是一个 Consumer  函数式接口的具体实现:/**  * Performs an action for each element of this stream.  */ void forEach(Consumer<? super T> action);
  具体使用的时候,每个方法中都需要传入具体函数式接口的实现逻辑,这个时候结合Lambda表达式,可以让代码更加的简洁干练(不熟悉的话,也可能会觉得更加晦涩难懂~),比如: public void testStreamUsage(@NotNull String sentence) {     Arrays.stream(sentence.split(" "))             .filter(word -> word.length() > 5)             .sorted((o1, o2) -> o2.length() - o1.length())             .forEach(System.out::println); } 利用函数式接口提升框架灵活度
  前面章节中我们提到,JDK中有预置提供了很多的函数式接口,比如 Supplier  、Consumer  、Predicate  等,可又分别应用于不同场景的使用。当然咯,根据业务的实际需要,我们也可以去自定义需要的函数式接口,来方便我们自己的使用。
  举个例子,有这么一个业务场景: 一个运维资源申请平台,需要根据资源规格不同计算各自资源的价格,最终汇总价格、并计算税额、含税总金额。
  比如:
  不同CPU核数、不同内存、不同磁盘大小的虚拟机,价格也是不一样的1M、2M、4M等不同规格的网络带宽的费用也是不一样的
  在写代码前,我们先分析下这个处理逻辑,并分析分类出其中的通用逻辑与定制可变逻辑,如下所示:
  因为我们要做的是一个通用框架逻辑,且申请的资源类型很多,所以我们显然不可能直接在平台框架代码里面通过 if else  的方式来判断类型并在框架逻辑里面去写每个不同资源的计算逻辑。
  那按照常规的思路,我们要将定制逻辑从公共逻辑中剥离,会定义一个接口类型,要求不同资源实体类都继承此接口类,实现接口类中的 calculatePirce  方法,这样在平台通用计算逻辑的时候,就可以通过泛型接口调用的方式来实现我们的目的:public PriceInfo  calculatePriceInfo(List resources) {     // 计算总价     double price = resources.stream().collect(Collectors.summarizingDouble(IResource::calculatePrice)).getSum();     // 执行后续处理策略     PriceInfo priceInfo = new PriceInfo();     priceInfo.setPrice(price);     priceInfo.setTaxRate(0.15);     priceInfo.setTax(price * 0.15);     priceInfo.setTotalPay(priceInfo.getPrice() + priceInfo.getTax());     return priceInfo; }
  考虑到我们构建的平台代码的灵活性与可扩展性,能不能我们不要求所有资源都去实现指定接口类,也能将定制逻辑从平台逻辑中剥离呢?这里,就可以借助自定义函数式接口来实现啦。
  再来回顾下函数式接口的要素是什么: 一个普通的JAVA interface类 此Interface类中有且仅有1个public类型的接口方法; (可选)添加个  @FunctionalInterface  注解标识。
  所以,满足上述3点的一个自定义函数式接口,我们可以很easy地就写出来: @FunctionalInterface public interface PriceComputer {     double computePrice(List objects); }
  然后我们在实现计算总价格的实现方法中,就可以将 PriceComputer  函数接口类作为一个参数传入,并直接调用函数式接口方法,获取到计算后的price信息,然后进行一些后续的处理逻辑:public  PriceInfo  calculatePriceInfo(List resources, PriceComputer priceComputer) {     // 调用函数式接口获取计算结果     double price = priceComputer.computePrice(resources);     // 执行后续处理策略     PriceInfo priceInfo = new PriceInfo();     priceInfo.setPrice(price);     priceInfo.setTaxRate(0.15);     priceInfo.setTax(price * 0.15);     priceInfo.setTotalPay(priceInfo.getPrice() + priceInfo.getTax());     return priceInfo; }
  具体调用的时候,对于不同资源的计算,具体各个资源单独计费的逻辑可以自行传入,无需耦合到上述的基础方法里面。例如需要计算一批不同规格的虚拟机的总价时,可以这样: // 计算虚拟机总金额 functionCodeTest.calculatePriceInfo(vmDetailList, objects -> {     double result = 0d;     for (VmDetail vmDetail : objects) {         result += 100 * vmDetail.getCpuCores() + 10 * vmDetail.getDiskSizeG() + 50 * vmDetail.getMemSizeG();     }     return result; });
  同样地,如果想要计算一批带宽资源的费用信息,我们可以这么来实现: // 计算磁盘总金额 functionCodeTest.calculatePriceInfo(networkDetailList, objects -> {     double result = 0d;     for (NetworkDetail networkDetail : objects) {         result += 20 * networkDetail.getBandWidthM();     }     return result; });
  单看调用的逻辑,也许你会有个疑问,这也没看出代码会有啥特别的优化改进啊,跟我直接封装两个私有方法似乎也没啥差别?甚至还更复杂了?但是看 calculatePriceInfo  方法会发现其作为基础框架的能力更加通用了,将可变部分的逻辑抽象出去由业务调用方自行传入,而无需耦合到框架里面了(很像回调接口的感觉)。
  函数式接口与Lambda的完美搭配
  Lambda 语法是JAVA8开始引入的一种全新的语法糖,可以进一步的简化编码的逻辑。在函数式接口的具体使用场景,如果结合Lambda表达式,可以使得编码更加的简洁、不拖沓。
  我们都知道,在JAVA中的接口类是不能直接使用的,必须要有对应的实现类,然后使用具体的实现类。而有些时候如果没有必要创建一个独立的类时,则需要创建内部类或者匿名实现类来使用: public void testNonLambdaUsage() {     new Thread() {         @Override         public void run() {             System.out.println("new thread executing...");         }     }.start(); }
  这里使用了匿名类的方式,先实现一个Runnable函数式接口的具体实现类,然后执行此实现类的  start()  方法。而使用Lambda语法来实现,整个代码就会显得很清晰了:public void testLambdaUsage() {     new Thread(() -> System.out.println("new thread executing...")).start(); }
  所以说,Lambda不是使用函数式编程的必需品,但是只有结合Lambda使用,才能将函数式接口优势发挥出来、才能将函数式编程的思想诠释出来。
  编程范式的演进思考
  前面的章节中呢,我们一起探讨了下函数式接口的一些内容,而函数式接口也是函数式编程中的一部分。这里说的函数式编程,其实是常见编程范式中的一种,也就是一种编程的思维方式或者实现方式。主流编程范式有命令式编程与声明式编程,而函数式编程也即是声明式编程思想的具体实践。
  那么,该如何理解命令式编程与声明式编程呢?先看个例子。
  假如周末的中午,我突然想吃鸡翅了,然后我自己动手,一番忙活之后,终于吃上鸡翅了(不容易啊)!
  为了实现"吃鸡翅"这个目的,然后是具体的一步一步的去做对应的事情,最终实现了目的,吃上了鸡翅。——这就是  命令式编程  。
  中午吃完烤鸡翅,我晚上还想再吃烤鸡腿,但我不想像中午那样去忙活了,于是我:
  照样如愿的吃上鸡腿了(比中午容易多了)。这里的我,只需要声明要吃鸡腿就行了,至于这个鸡腿是怎么做出来的,完全不用关心。——这就是  声明式编程  。
  从上面的例子中,可以看出两种不同编程风格的区别: 命令式编程 的主要思想是关注计算机执行的步骤,即一步一步告诉计算机先做什么再做什么 。各种主流编程语言如C、C++、JAVA等都可以遵循这种方式去写代码。声明式编程 的主要思想是告诉计算机应该做什么,但不指定具体要怎么做 。典型的声明式编程语言,比如:SQL语言、正则表达式等。
  回到代码中,现在有个需求:
  从给定的一个数字列表collection里面,找到所有大于5的元素,用命令式编程的风格来实现,代码如下:  List results = new ArrayList<>(); for (int num : collection) {     if (num > 5) {         results.add(num);     } }
  而使用声明式编程的时候,代码如下: List results =      collection.stream().filter(num -> num > 5).collect(Collectors.toList());
  声明式编程的优势,在于其更关注于" 要什么 "、而会忽略掉具体怎么做。这样整个代码阅读起来会更加的接近于具体实际的诉求,比如我只需要告诉 filter  要按照 num > 5  这个条件来过滤,至于这个filter 具体是怎么去过滤的,无需关心。
  总结
  好啦,关于函数式接口相关的内容,就介绍到这里啦。那么看到这里,相信您应该有所收获吧?那么你对函数式编程如何看呢? 评论区一起讨论下吧、我会认真对待并探讨每一个评论 ~~
  此外 :关于本文中涉及的 演示代码 的完整示例,我已经整理并提交到github中,如果您有需要,可以自取:https://github.com/veezean/JavaBasicSkills
  我是悟道,聊技术、又不仅仅聊技术~
  如果觉得有用,请 点赞 + 关注 让我感受到您的支持。也可以关注下我的公众号【架构悟道】,获取更及时的更新。
  期待与你一起探讨,一起成长为更好的自己。

盘点甄子丹电影中十大高手,张天志仅排第五,封于修位居前三你觉得谁才是甄子丹电影中,实力最强的高手,有人可能会说叶问张天志,或是导火线汤尼。其实甄子丹电影中还有不少高手出现,小虎翻阅不少资料,为大家盘点一下甄子丹电影中十大高手。第十杀破狼同样是一个人采访,为何称呼不一样,你们帮谁?1。hr今天我对儿子说,真羡慕你小子,每天只是上上学,哪像我上完班还要照顾你妈,你不知道伺候一个多事老娘们儿有多麻烦?儿子不服气说道,你以为上学轻松呀,你不过是伺候一个女人,我要伺漫谈我国能源运输命脉当前,我国新能源革命如火如荼,光伏产业新能源汽车等行业蓬勃发展,已经诞生了比亚迪宁德时代等万亿市值的行业巨头,其中比亚迪更是成为我国汽车行业弯道超车的希望。1992年钱学森曾向时任拜登刚到沙特就被打脸,参议院阻挠对中东国家军售据环球网报道,近日美国众议院通过了价值8400亿美元的国防授权法案,为五角大楼制定预算并指导美国2023年的军事政策。今年的国防授权法预算比以往任何一年都要高,甚至超过了拜登的预算解放军驱赶美军机后,美国防部扯淡称行为合法别国也能这么做7月15日美媒政客报道称根据2名五角大楼知情人士透露称,在上个月一架解放军苏30战机,曾和美军一架经过特种改装的C130运输机,在中国南海上空遭遇,苏30战机做出了各种不安全和不专男篮杀入亚洲杯决赛!前提是,淘汰劲敌国际雇佣军黎巴嫩图对阵台北周琦隔扣阿提诺1东方巨龙正在慢慢苏醒徐杰恢复了正常轮换,王哲林也已经打了两场了,周琦也刚刚在对阵台北的比赛中上演英雄归来,被疫情下了降头的中国男篮终于驱散了瘴气,面对台北小国破产,总统秘密逃亡,欠中国的钱怎么办?汪文斌作出回应据中国青年网报道,近期就斯里兰卡面临的困难和挑战问题,中方外交部发言人表示,中方将继续在力所能及范围内为斯里兰卡经济社会发展提供帮助中方支持有关金融机构同斯方协商,妥善解决斯方涉华中国男篮15分大胜,周琦复出,全队6人得分上双,下场对阵印尼队亚洲杯小组赛第三场,中国队以9580大胜中国台北队,分差达到15分,本场比赛,中国队派出王哲林顾全赵睿胡明轩孙铭徽首发,周琦迎来复出,第一节,中国队气势如虹,打出一波进攻高潮,瞬间荷尔蒙旺盛的女人,通常有什么特征呢?不妨了解荷尔蒙也叫激素,源于希腊语,是高度分化的内分泌细胞,合成并直接分泌进入体液的化学信息物质,在生理浓度范围内作用在靶细胞,引起有效生理效应。激素主要作用是调节人体新陈代谢,激素也具有观世音菩萨是男相还是女相?观音信仰,在中国民间可谓家喻户晓。只要说起观世音菩萨,我们脑海里立刻浮现一位左手托净瓶,右手拿柳枝,脚踏莲台,法像慈祥端庄的女性形象。所以,一般人看来观世音菩萨就是一位美丽善良的女韩雪才是真的时髦精,穿一身黑色服装霸气时髦,涂红唇变冷艳美人夏日生活打卡季就要这么穿在服装的穿搭上,总是有那么一些色彩不具备光明性,但是穿起来却无比的精致有型,就像黑色服装,这种颜色真的满足了很多女性的穿着需要,既带着格调,还很有气场,即便
浙江队大胜福建豪取十连胜盖利2114浙江大破福建豪取十连胜虎扑12月8日讯CBA第十轮今日继续进行,开季不败的浙江面对福建的挑战,王化东上篮得手拉开比赛大幕,梅克和黎伊扬内线造杀伤罚球全中帮助福建拿下先手包机ampampquot抢订单ampampquot!浙苏川闵粤等地组团出海,打响外贸保卫战!运价暴跌60,ampampquot一箱难求ampampquot不再多地政府包机出海抢订单。在今年地缘政治冲突能源成本飙升通胀攀升的复杂严峻形势下,全球整体需求疲软,经济下行压力加大,中国外贸企业订单出现旺季不旺现象。全球贸易萎缩,集装箱需求锐减,4000左右的电动车,出厂价多少?卖一台商家赚多少?答案告诉你请您在阅读前,先点击上面的关注。感谢您的支持,我们将为您带来更多有价值的内容。在国内各种出行代步工具中,电动车可谓备受欢迎的存在了。即便有车一族在短途通勤代步这一块,还是会选择电动科技赋能助力北京打好污染防治攻坚战中国工业报记者曹雅丽12月5日,中国工业报记者从北京市科技赋能打好污染防治攻坚战新闻发布会上获悉,近五年来,围绕国家和北京市污染防治攻坚科技支撑需求,北京市生态环境保护科学研究院(物产中大云商廉矩阵打造新消费领域清廉朋友圈选树优秀标杆凝聚榜样力量抓好典型传播近年来,浙江国资国企各级党组织担当作为攻坚克难创新制胜,以高质量党建引领高质量发展,充分发挥了国有企业把方向稳经济促发展的作用。第四届国企党建创年产能达2亿平方米,我国显示面板产业规模达全球第一北京日报客户端实习记者夏骅记者日前从工信部获悉,近年来,我国新型显示产业不断跑出加速度迈上新台阶,显示面板年产能达到2亿平方米,产业规模跃居全球第一。我国新型显示产业保持高速增长态华润成都熔断项目曝地产私募介入,涉西藏信托3。7亿来源地产密探(IDrealestatespy)编辑密探财经近期房地产金融大破局,让房企突然感到幸福来得太快了吧。但相对于十多家银行争前恐后意向性授信近两万亿,但毕竟还是画大饼阶段,我的小村庄龙洞村的诗社视频加载中编者按我的小村庄是展示长沙美丽乡村的一个系列微纪录片。小切口小人物小故事,有大背景。片子选取了五个在乡村追寻梦想的年轻人,分五个篇章诗和梦想的路有温度的泥巴龙洞村的诗社山山东这10个小村不简单!上榜国家榜单,每一处都美到脱俗说起乡村,您想到的是什么?是草长莺飞二月天,拂堤杨柳醉春烟,还是阡陌交通,鸡犬相闻?前几日,农业农村部办公厅公布了2022年中国美丽休闲乡村名单,其中山东这10个地方入选!建设美丽文笔村是大理洱海边的一处世外桃源,游客基本不知道它的存在我这一次到大理,特意选择一个极为安静的地方住下来,那里便是与大理古城隔洱海相望的海东镇文笔村。文笔村没有旅游大巴和熙熙往往的游客,却有最迷人的苍海风光时尚浪漫的民宿古朴幽静的村舍。洗缩水的毛衫,真能恢复如初吗?软糯的羊绒衫,暖身的毛衣,绝对是冬日衣橱里的主角,然而,一个不小心洗错了,M变成XS的惨剧就这样发生了。不过近来市面上开始出现一些还原神器专门给洗缩水了的毛衫等羊毛羊绒制品用的洗涤