八、 Lambda表达式使用经验
下面,将介绍函数式编程和Lambda表达式的一些使用经验。
1. 规避传入可变参数
由于Lambda表达式只接受final不可变参数,或没有final修饰的无修改参数。如果希望传入的参数可修改,可以利用对象(容器对象不可变,但容器内容可变)解决。
如上所示,利用AtomicInteger解决了序号递增的问题。
2. 避免自定义函数式接口
为了减少代码中自定义函数式接口,可以利用Java提供的4个基本函数式接口:
• Supplier(提供者):没有输入参数,返回一个T类型的值;
• Consumer(消费者):接收一个T类型的参数,并且没有返回值;
• Function,R>(函数):接收一个T类型的参数,返回一个R类型的值;
• Predicate(断言):接收一个T类型的参数,返回一个boolean类型的值。
在包“java.util.function”中,Java提供了大量的函数式接口,大家可以实际需求选择使用。
• 原始代码
• 优化代码
3. 解决延迟调用问题
尽量使用方法引用传递代替值传递,可以让方法在真正使用时才调用,避免一些不必要的方法计算,我们称之为“延迟调用”。
在下面代码中,无论“localValue”是否为空,都将执行“getRemoteValue”方法获取值。
那么,我们可以利用“Optional”的“orElseGet”方法实现延迟调用:
4. 解决初始化计算问题
根据模板工厂设计模式,需要抽象出一个或多个方法,并实现一个固定的算法流程。如果在算法流程中,循环地调用某一个方法,并且在这个方法中都要计算同一套参数,就存在一个初始化计算问题——应该提前初始化这些参数。
如果这些参数固定(每个实现类都用这套参数),我们可以抽象出一个初始化参数的虚方法,利用这个方法计算得出参数值,然后再把这些参数传入对应的方法。但是,如果这些参数不固定(根据实现类不同而不同),就不能采用这个方法了。
这里,作者给出一个基于Lambda表达式(函数式编程)的解决方案。
如上所示:首先计算出参数k1、k2值,再返回一个基于这些参数实现的方法引用。主流程使用这个方法引用判断用户是否有效,从而避免了每次调用对参数k1、k2值的计算。
5. 解决类型推断冲突问题
有时候,重载的两个方法(方法名完全一致),为了区分是否有返回值,需要定义两个不同的参数类型。如果,这两个参数类型是函数式接口,且这两个函数式接口定义完全一致,在使用Lambda表达式时,就会出现类型推断冲突的问题。
通常,我们会如下编写Lambda表达式:
但是,这个时候编辑器就会报出以下错误:
那么,我们需要加上类型强制转换:
但是,SonarLint还会出现以下警告:
最后,给出最完美的解决方案:
6. 简化常用的设计模式
在软件开发过程中,软件设计模式无处不在。通过函数式编程,减少了不必要接口声明及类实现,能够让设计模式的运用更灵活。
比如下面的代码中,明显有很多的通用代码。如果按照模板方法模式(Template Method Pattern)进行封装实现,将会编写大量的模式代码。
如果通过函数式编程和Lambda表达式实现,代码量将会降到最低。
7. 优化多表达式条件语句
有时候,我们会判断很多条件,需求用&&(或||)连接多个条件表达式。
通过SonarLint插件扫描,会存在2个复杂度问题:
• Expressions should not be too complex(表达式不能太复杂);
• Methods should not be too complex(方法不能太复杂)。
这里,可以采用Lambda表达式进行优化,降低代码复杂度并避免上述问题。
后记
《如梦令·迎雪夜归》
冬日寒风砭骨,皓月冷光铺路。
一骑夜归迟,迎雪下车徒步。
何苦?何苦?即兴作诗吟赋!