转载本文请联系JavaToday公众号。前言第一次按类型筛选瓜第二次按重量筛选瓜第三次按类型和重量筛选瓜第四次将行为作为参数传递第五次一次添加100个筛选条件第六次是时候简要介绍泛型了Lambda总结前言大家好,我叫Milo。我又回来了??进来给大家八卦一下,看看我都干了什么?听说公司最近接手了一个新的农产品交易网站项目,因为一个代码重构的问题,差点被老板找上门来。我以为是老板故意刁难我。结果我发现我太优秀了?事情是这样的!周会上,老板告诉我们,最近接到了一个农产品交易平台,主要用于全省农产品的网上交易。首先要做的是卖甘肃的黄河蜂蜜,所以被安排卖瓜!哦,不是,我负责开发卖瓜功能?;很快我设计了下面的类来定义Melon类:/***Melon*@authorMiloLee*@date2021-04-0713:21*/publicclassMelon{/**species*/privatefinalStringtype;/**weight*/privatefinalintweight;/**origin*/privatefinalStringorigin;publicMelon(Stringtype,intweight,Stringorigin){this.type=type;this.weight=weight;this.origin=origin;}//getters,省略toString()方法}一次CRUD操作后,增删改查完瓜,交下班了。第一次按类型筛选瓜第二天,老大问我一个问题,说增加按类型筛选瓜的功能。这不是很简单吗?因此,我创建了一个Filters类并实现了一个filterMelonByType方法/***@authorMiloLee*@date2021-04-0713:25*/publicclassFilters{/***按类型过滤甜瓜*@parammelonsmelons*@paramtypetype*@return*/publicstaticListfilterMelonByType(Listmelons,Stringtype){Listresult=newArrayList<>();for(Melonmelon:melons){if(melon!=null&&type.equalsIgnoreCase(melon.getType())){result.add(melon);}}returnresult;}}完成,让我们测试一下:publicstaticvoidmain(String[]args){ArrayListmelons=newArrayList<>();melons.add(newMelon("冠蜜",1,"泰国"));melons.add(newMelon("西瓜",2,"三亚"));melons.add(newMelon("黄河蜜",3,"兰州"));ListmelonType=Filters.filterMelonByType(melons,"黄河蜜");melonType.forEach(melon->{System.out.println("melontype:"+melon.getType());});}没问题,拿给老大看,老大看了我的代码说:如果我让你按重量加个瓜的过滤器,你打算怎么写?回头想想,这家伙不会是故意找我的吧???第二次回座位按重量筛选瓜,心想,上次实现按类型筛选瓜,那我给他发一份改一下!如下图:/***按重量过滤瓜*@parammelons*@paramweight*@return*/publicstaticListfilterMelonByWeight(Listmelons,intweight){Listresult=newArrayList<>();for(Melonmelon:melons){if(melon!=null&&melon.getWeight()==weight){result.add(melon);}}returnresult;}publicstaticvoidmain(String[]args){ArrayListmelons=newArrayList<>();melons.add(newMelon("皇冠蜜",1,"泰国"));melons.add(newMelon("西瓜",2,"三亚"));melons.add(newMelon("黄河蜜",3,"兰州"));ListmelonType=Filters.filterMelonByType(melons,"黄河蜜");melonType.forEach(melon->{System.out.println("瓜类:"+melon.getType());});ListmelonWeight=Filters.filterMelonByWeight(melons,3);melonWeight.forEach(melon->{System.out.println("melonweight:"+melon.getWeight());});}程序员最喜欢的方式,CV搞定了,哈哈不过我发现filterByWeight()和filterByType()很像,只是过滤条件不同而已。我心想,老板不会让我按种类和重量写筛选瓜的。拿着我的代码拿给老大看,果不其然,就怕什么来。第三次按类型和重量筛选瓜为了满足老板的任务,我结合上面的代码,很快写了如下代码:/***按类型和重量筛选瓜*@parammelons*@paramtype*@paramweight*@return*/publicstaticListfilterMelonByTypeAndWeight(Listmelons,Stringtype,intweight){Listresult=newArrayList<>();for(Melonmelon:melons){if(melon!=null&&type.equalsIgnoreCase(melon.getType())&&melon.getWeight()==weight){result.add(melon);}}returnresult;}老大看了我的代码说,看来你还是没明白我的意思.如果今天不仅是我,还有客户继续这样提出要求就好了。那么Filters就会有很多这样类似的方法,也就是说要写很多样板代码(代码冗余但不得不写);在我们程序员看来,这是不可接受的。如果您不断添加新过滤器,代码将变得难以维护且容易出错。你去学习lambda表达式和函数式接口知识点,然后修改你的代码。我敢肯定,他只是找我麻烦?第四次将行为作为参数经过上面的三次折腾。我发现理论上Melon类的任何属性都可以作为过滤条件,所以我们的Filter类会有很多样板代码,有些方法会很复杂。其实我们可以发现,我们写的每一个方法都对应一个查询行为,而查询行为必然对应一个过滤条件。有没有办法让我们写一个方法,将查询行为作为参数传入,从而返回我们的结果?于是有了一个名字:BehaviorParameterization,如下图所示(左边是我们现在拥有的;右边是我们想要的),你发现样板代码会明显减少吗??如果我们把过滤条件看成一个行为,那么很直观的把每一个行为看成是接口的实现。经过分析,我们发现以上所有行为都有一个共同点:过滤条件和返回boolean类型。一个抽象接口如下:publicinterfaceMelonPredicate{booleantest(Melonmelon);}例如过滤黄河蜂蜜可以这样写:HHMMelonPredicate。publicclassHHMMelonPredicateimplementsMelonPredicate{@Overridepublicbooleantest(Melonmelon){return"YellowRiverHoney".equalsIgnoreCase(melon.getType());}}以此类推,我们也可以过滤一定权重的瓜类:5000;}}其实熟悉设计模式的同学应该都知道这是:策略设计模式。主要思想是让系统在运行时动态选择需要调用的方法。所以我们可以认为MelonPredicate接口统一了所有专门用于筛选瓜的算法,每一个实现都是一种策略,我们也可以理解为一种行为。目前,我们使用策略设计模式来抽象查询行为。我们还需要一个带有MelonPredicate参数的方法。所以我定义了filterMelons()方法如下:null&&predicate.test(melon)){result.add(melon);}}returnresult;}大功告成,测试一下,比以前好用多了,让老大看看Listhhm=Filters.filterMelons(melons,newHHMMelonPredicate());Listweight=Filters.filterMelons(melons,newWeightMelonPredicate());第五次一次性加100个过滤条件正当我得意洋洋的时候,老大又给他泼了一盆冷水。他说你以为我们平台只买黄河蜂蜜。如果有几十个瓜品种,我给你列出100种过滤条件,你怎么办?万匹草泥马在心中奔腾??!老大是不是想刁难我!虽然经过上次改造后我的代码已经足够灵活了,但是如果突然加100个过滤条件,我还是需要写100个策略类来实现每个过滤条件。然后我们需要将策略传递给filterMelons()方法。有没有不需要创建这些类的方法?聪明的我很快发现java匿名内部类是可以用的。如下图:Listeuropeans=Filters.filterMelons(melons,newMelonPredicate(){@Overridepublicbooleantest(Melonmelon){return"europe".equalsIgnoreCase(melon.getOrigin());}});大步前进,但似乎无济于事。我还需要写很多代码来实现这个需求。设计匿名内部类的目的是为了方便Java程序员将代码作为数据传递。有时,匿名内部类看起来更复杂。这时候突然想起老大让我学的lambda表达式。我可以用它来简化:ListeuropeansLambda=Filters.filterMelons(melons,m->"europe".equalsIgnoreCase(m.getOrigin()));果然这样帅多了!!!,就这样,我又一次圆满完成了任务。我兴奋地接过代码,让老大看一下。第六次引入泛型,老大看了我的代码说,嗯,不错!我的脑袋终于打开了。现在大家想一想,我们这个平台是做农产品的,也就是说,一定不能只有瓜类这种水果。如果你换成其他水果,你如何修改你的代码?目前我们的MelonPredicate只支持Melon类。这家伙做什么?也许有一天他想买蔬菜和海参,但是他不能为他创建很多类似于MelonPredicate的接口。这时候突然想起老师说的泛型,是时候发挥作用了!所以我定义了一个新的接口Predicate:@FunctionalInterfacepublicinterfacePredicate{booleantest(Tt);}接下来我们重写filterMelons()方法,重命名为filter():publicstaticListfilter(Listlist,Predicatepredicate){Listresult=newArrayList<>();for(Tt:list){if(t!=null&&predicate.test(t)){result.add(t);}}returnresult;}现在,我们可以像这样过滤西瓜:Listwatermelons=Filters.filter(melons,(Melonm)->"Watermelon".equalsIgnoreCase(m.getType()));同样,我们可以对数字做同样的事情:Listnumbers=Arrays.asList(1,13,15,2,67);ListsmallThan10=Filters.filter(numbers,(Integeri)->i<10);回过头来看,我们发现自从使用Java8函数式接口和lambda表达式以来,代码发生了显着变化。不知道细心的小伙伴有没有注意到,上面的Predicate接口上多了一个@FunctionalInterface注解,标明是函数式接口。至此,我们通过一个需求演进的过程,了解了lambda和函数式接口的概念,同时加深了对它们的理解。其实熟悉java8的朋友都知道,我们的java.util.function包中包含了40多个这样的接口。函数式接口和lambda表达式组成了一个强大的团队。根据上面的例子,我们知道函数式接口是对我们行为的高层抽象,我们可以在lambda表达式中看到这个行为具体实现的例子。Predicatepredicate=(Melonm)->"西瓜".equalsIgnoreCase(m.getType());简而言之,Lambdalambda表达式由三部分组成,如下图所示:下面是对lambda表达式各部分的说明:箭头左边,是lambda表达式体中用到的参数.箭头右侧是lambda体。箭头只是lambda参数和正文的分隔符。这个lambda的匿名类版本如下:}});现在,如果我们看一下lambda表达式和它们的匿名类版本,lambda表达式可以用四种方式来描述:功能。虽然它不属于特定的类,但它有参数列表、函数体、返回类型,甚至可以抛出异常;其次,它是匿名的,lambda表达式没有具体的函数名;lambda表达式可以像参数一样传递,简化代码编写。Lambda支持行为参数化,我们在前面的示例中已经对此进行了演示。最后,请记住lambda表达式只能在函数式接口的上下文中使用。总结在本文中,我们强调了函数式接口的有用性和可用性,我们将研究代码如何从样板文件演变为基于函数式接口的灵活实现。希望对大家理解功能接口有所帮助,谢谢。