零、概述
- Lambda可以提升代码的可读性和灵活性;
- 尽量用Lambda 替换匿名内部类;
- 用方法引用优化Lambda表达式;
- 尽量用 Stream API 优化集合的迭代处理;
- 使用Lambda优化响应设计模式代码;
- Lambda表达式的测试;
- 尽量将复杂的Lambda抽象到普通方法中;
- Lambda堆栈信息复杂难以看懂;
- peek方法可以将中间变量打出日志。
一、重构
- 使用Lambda 替代匿名内部类。
- 方法引用重构Lambda表达式。
- 用StreamAPI重构命令式数据处理。
1、Lambda和匿名内部类区别
- 匿名内部类中的this和Lambda中指代的是不一样的【匿名内部类中指的是类自身】【Lambda中指代的是包含的类】,匿名内部类可以屏蔽掉包含类的变量,但是Lambda无法屏蔽掉。
2.匿名内部类的的类型是在初始化时确定的,而Lambda的类型则取决于它的上下文。
我们有两个接口A、B 都有各自的函数式接口方法doA(),doB().public static void doSomething(A a){ a.doA(); } public static void doSomething(B b){ b.doB(); }
上面的用匿名内部类来实现是没有问题的,但是在使用Lambda 实现的时候如下:
doSomething(() -> System.out.println("AAABBB")) ;
这时候A、B接口都是符合该Lambda的所以让人混乱。我们可以显示的标注出来如下:
doSomething((A)() -> System.out.println("AAABBB"));
2、Lambda表达式到方法引用
经过之前的了解我们知道Lambda非常适合传递代码片段的应用场景。
Integer[] interArray = new Integer[]{1,2,3,4};
System.out.println(Arrays.stream(interArray).reduce(0,(a,b) -> a+b));
System.out.println(Arrays.stream(interArray).reduce(Integer::sum).get());
- 上面的代码我们将数组所有的元素进行求和运算。
- 第一个输出语句 使用Lambda 传递两个参数 然后求和操作。
- 第二个输出语句我们方法引用进行操作。
- 因为我们发现第一个输出语句其实就是在调用Integer的sum函数。
- 综上所述:
我们在编写代码的时候,可以尽量的将我们的某些方法抽象出来,然后使用方法引用替换Lambda表达式,让我们代码更加易懂。
3、其他
在我们的日常编码中经常出现下面这样的操作:
List<FundProductDynamicInfoDO> infoDOS = productShowService.queryBurstingFundInfo();
infos = Lists.transform(infoDOS, new Function<FundProductDynamicInfoDO, FundProductDynamicInfo>() {
@Override
public FundProductDynamicInfo apply(FundProductDynamicInfoDO fundProductDynamicInfoDO) {
FundProductDynamicInfo info = new FundProductDynamicInfo();
BeanUtils.copyProperties(fundProductDynamicInfoDO, info);
return info;
}
});
或者下面这样的:
List<FundProductRatedetailInfo> fundProductRatedetailInfosId = dealModifyRateListForID(fundProductRatedetailInfosAll);
if(fundProductRatedetailInfosId != null && fundProductRatedetailInfosId.size() > 0){
for(FundProductRatedetailInfo po: fundProductRatedetailInfosId){
fundProductRatedetailInfoMapper.updateByPrimaryKeySelective(po);
}
}
我们应该采用下面这种来进行替代:
List<User> users = new ArrayList<>();
users.add(bUser.apply("18","zhangsan13"));
users.add(bUser.apply("15","zhangsan15"));
users.add(bUser.apply("12","zhangsan12"));
users.add(bUser.apply("14","zhangsan141"));
List<Integer> userNameLength = users.parallelStream().map(User::getName).map(String::length).collect(Collectors.toList());
结果:
userNameLength:[10, 10, 10, 11]
我们在使用Lambda表达式进行行为参数化的时候,需要注意以下几点:
- 我们必须要明确一点就是只有在可以使用函数式接口的地方才可以使用Lambda表达式,具体来讲在:有条件的延迟执行、环绕执行我们使用。
- 有条件的延迟执行,如果我们需要频繁的去从客户端代码查询一个对象的状态,只是为了传递参数、调用该对象的一个方法,那么我们可以考虑实现一个新的方法,以Lambda或者方法表达式作为参数,新方法在检查完该对象的状态后才调用原来的方法,这样我们的代码会更加易读,且封装性很好。
- 环绕执行,如果发现业务代码都有着公共的准备和结束阶段的代码,我们完全可以使用Lambda表达式将这部分抽离出来。
- 使用Lambda表达式重构设计模式:
策略模式
模板模式
观察者模式
责任链模式
工程模式
4、关于设计模式的重构
策略模式
模板模式
观察者模式
责任链模式
工程模式
-
策略模式
策略模式代表了一类算法的通用解决方案。包含以下三部分
- 一个代表某个算法的接口【策略模式的接口(也是函数式接口)】
- 一个或者多个该接口的具体实现,他们代表了算法的多种实现
- 一个或者多个使用策略对象的客户
我们在构建使用策略的客户的时候需要传递响应的策略,所以我们可以使用Lambda表达式来避免那些模板代码。
-
模板模式
- 模板方法在我们需要使用某个算法,但是需要对其中的一些行为进行改进的时候可以使用。
- 我们可以使用Consumer< T > 的 accept() 对功能进行差异化修改。
-
观察者模式
某些事件发生时【状态改变】,如果一个对象【主题】需要自动的通知其他多个对象【观察者】,就可以使用观察者模式。
- 主题中维护着一个观察者列表【存储观察者】一个注册方法【添加观察菏泽】一个通知方法【通知观察者】,观察者 实现 具体通知方法
- 我们注意到观察者都实现了notify()方法,那么我们可以使用Lambda表达式消除掉这种僵化的代码。
- 我们要根据具体逻辑来判断要不要使用Lambda表达式来消除这些代码。
-
责任链模式
责任链模式是一种创建处理对象序列【比如操作序列】的通用方案 。一个处理对象可能需要在完成一些工作后,将结果传递给另一个对象,这个对象接着做一些工作,在转给下一个处理对象,以此类推。
- 我们可以使用Function<T ,R > 的andThen函数来对其进行构造。
-
工程模式
使用工厂模式我们不需要暴露实例化的逻辑就可以完成对象的创建。
-
人类接口及实现 将够赞函数放入map中
private interface Human {} static private class Black implements Human {} static private class White implements Human {} static private class Yellow implements Human {} final static private Map<String, Supplier< Human>> map = new HashMap<>(); static { map.put("black", Black::new); map.put("white", White::new); map.put("yellow", Yellow::new); }
-
工厂方法
static private class HumanFactory { public static Human createHuman(String name){ switch(name){ case "black": return new Black(); case "white": return new White(); case "yellow": return new Yellow(); default: throw new RuntimeException("No such Human " + name); } } public static Human createHumanLambda(String name){ Supplier< Human > h = map.get(name); if(h != null) return h.get(); throw new RuntimeException("No such Human " + name); } }
-
测试
Human h1 = HumanFactory.createHuman("black"); Supplier< Human> blackSupplier = Black::new; Human h2 = blackSupplier.get(); Human h3 = HumanFactory.createHumanLambda("black");
-
二、测试
因为Lambda函数是匿名函数,所以我们在测试的时候没有办法向之前的有名函数那样就行跟进。那么我们怎么进行测试?
- 我们可以引入变量,将Lambda的结果赋值给该值,根据值得行为去测试。
- 因为我们是将逻辑封装起来给另一个方法使用,所以我们亦可以通过测试需要Lambda表达式作为行为参数的方法的行为来测试Lambda。
- 拆分复杂的Lambda表达式到不同的方法。
- 对高阶函数【接受函数作为参数的方法或返回一个函数的方法】
三、调试
- 根据堆栈信息去理解相关报错。
- 添加日志的方式去调试。