匿名表达式(lambda)的核心,就是将代码体以参数的方式进行传递。Java里面有很多类似的思想,比如泛型就是把类型作为参数的方式传递,其中的精髓都是起着解耦和复用的作用。
而函数式编程是一种编程范式,是独立于面向过程和面向对象编程范式的。函数式编程的核心,就是把函数整体作为参数进行传递,把业务逻辑全部解耦成函数,并且通过函数的相互组合和复用形成更大的函数,最终实现业务逻辑。
而Java的核心是面向对象编程,天然就不具备函数式的特征,即无法把函数体本身进行传递,在早先的时候,一直是使用内部对象的方式来实现方法体的传递。而Java8中新增的Java函数式编程就是为了解决这个问题的。
利用Java匿名表达式实现回调
回调的概念如其名称,即“回”。“回”的本质就是A调用B的时候,B再调用结束前又调用A。因此在回调的经典案例代码一般是如下所示:
//调用方
class ClientA implement CallBack{
public void function(){
B b = new B();
//发起调用
b.process(new ClientA());
}
public void call(){
//回调逻辑
}
}
//被调用方
class B{
void process(CallBack callBack){
//处理逻辑
...
//发起回调
callBack.call();
}
}
//回调接口
interface CallBack{
void call();
}
回调的核心,就是向被调用方注册调用的回调接口,当被调用方处理完毕业务逻辑后,发起对回调代码的处理。实际上的回调会有很多变种,比如把回调部分拆分出来成为单独的类以做更大的解耦。
例如下面的一个实际生成环境的例子,需要在页面处理的时候,给页面覆写一个页面的生成ID。正常的写法是在页面处理Main方法里先查询出来该ID,然后再SET这个取得的ID。由于生成的ID的代码比较复杂,所以把生成ID的代码封装成了一个单独的方法,使得ID生成的方法成为了一个被调用方。
- 原先的写法
// 在Main方法里调用覆盖页面ID
String Id = this.getPageId(context);
context.setId(Id);
;
/**
* 获取用户组织除了常用页面appUuid之外的第一个有效的页面uuid
*
* @param context
* @return
* @throws WorkbenchException
*/
private String getPageId(SchemaContext context) {
// 获取组织生效的页面ID
String adapterAppUuid = orgWorkbenchSupport
.getUuid(context.getOrgId(), context.getAppUuid(), context.getUid());
// 这里还隐藏了复杂的逻辑
return adapterAppUuid;
}
可以看到,这是一种典型的面向过程的写法,没有太大的问题。但是从这里也可以看出getPageId只提供该逻辑使用,没有需要复用的场景;其次把一些ID的设置逻辑散落到了Main方法里,不够内聚。这里就可以使用匿名表达式的方式,结合回调的思想,把代码进行优化。
- 匿名表达式回调写法
首先是回忆一下回调的核心思想,就是【向被调用函数先注册一个回调钩子,然后被调用函数在执行到某个步骤的时候,执行钩子方法实现一个“回调”的逻辑】。用业务上的术语来描述,就是“代码再执行完毕A逻辑后,再执行B逻辑”,用这种思想可以把一个复杂的流程拆成多个流程,进而在代码层面上实现解耦。
比如上面实际生产的例子,就可以概括成下面的算法逻辑:
1.向被调用代码注册一个函数式接口
2.被调用代码编写回调逻辑
3.调用方调用被调用方法,并传递函数表达式
具体优化后的代码如下所示
// 在Main方法里调用覆盖
this.getPageId(context,uuid ->{
context.setAppUuid(StringUtils.isBlank(uuid) ? appUuid : uuid);
});
/**
* 获取用户组织除了常用页面appUuid之外的第一个有效的页面uuid
*
* @param context
* @return
* @throws WorkbenchException
*/
private void getPageId(SchemaContext context, Consumer<String> buildContextAppUuid) {
// 获取组织生效的页面ID
String adapterAppUuid = orgWorkbenchSupport
.getEnabledAppUuidsByRequestAppUuid(context.getOrgId(), context.getAppUuid(), context.getUid());
// 这里还隐藏了复杂的逻辑
//把appUuid传递覆写
buildContextAppUuid.accept(adapterAppUuid);
}
上面的getPageId就是起着一个被调用函数的作用,我们通过注册Consumer<String> buildContextAppUuid到getPageId函数上,告诉getPageId函数,当你执行完毕后再回调一下buildContextAppUuid方法。由于函数体比较简单,只有一句context.setId(Id),如果函数体特别复杂的情况下,我们还可以把函数体也抽象成一个接口实现,实现更彻底的解耦。
函数式接口本质即把代码体当成函数传递,当A调用B的时候,向B注册函数钩子(函数式接口),在B执行回调的时候,可以直接传递函数式表达式。即用一种更加优雅简洁的方式完成了回调函数的写法。但是值得注意的是,这种方式仅适用于函数式代码逻辑不多的场景,如果回调的代码非常多,则会使得方法A中大量暴露回调的详细逻辑,对于代码的可读性和可维护性都会降低。
比如上文这种在调用B查询ID并在A中覆盖ID这种轻量级逻辑,就可以用函数表达式的方式进行处理,就更加优雅、已读。
函数式接口介绍
函数式接口就是对于函数表达式的标准接口定义,也就是一个函数接口背后可以实现多种函数式代码。Java8里已经提供了多种默认的函数式接口的实现,大家可以直接取用。
也可以自定义自己的函数式接口,定义起来也比较方便。定义格式如下:
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
}
即用FunctionalInterface注解来标注接口,就成为了函数式接口。使用的时候就可以以函数式语法来执行:
GreetingService greetService = message-> System.out.println("Hello " + message);
更多函数式接口明细:
https://www.runoob.com/java/java8-functional-interfaces.html
更多精彩文章,可以关注公众号:ali老蒋