自动JDK8诞生,基于stream api的程序编码成为程序员秀代码的平台。事实上,除了stream相关api外,提供的lambda表达式,更新精简了不少代码。然而,对于大多数开发者而言,灵活运用基于函数式编程依然窃取,倘若灵活使用jdk8的相关函数式编程,可以有效精简我们的冗余代码,使得我们的代码可以更灵活的复用。这边,今天来谈谈JDK8中的Function具体该如何更灵活的运用,相信通过这篇文章,你有所收获。
最近要完成一个小功能,该功能在一个工程中有类似业务逻辑实现,我了解了一下,顺便就可以快速实现这个功能模块开发。
现有一个Business类中有如下一个方法
public void syncVideoToDp(List<Long> ids) {
int pageNo = 1;
while (true) {
try {
List<RecruitShortVideoBO> videoList = recruitShortVideoDAO.listAllLiveVideo(ids, pageNo);
if (CollectionUtils.isEmpty(videoList)) {
break;
}
sendUpdateUrlEvent(videoList);
pageNo = pageNo + 1;
} catch (Exception e) {
logger.error("异常:" + e);
break;
}
}
}
上述方法,通过梳理逻辑,可以得出该方法的主要功能就是如下
List
类型,方法是void返回类型。recruitShortVideoDAO
查询数据集,如果数据集为空,则跳出循环;不为空,则吧查询的数据集进而调用成员方法sendUpdateUrlEvent
。而我呢,有个功能业务逻辑跟如上方法类似,区别在于接口的方法参数不一样,调用dao的另外一个方法,依然是分页查询,没有数据集则跳出循环。于是,实现逻辑复制如上代码然后最终实现如下:
public void syncNearlyVideoToDp(Instant fromTime) {
int pageNo = 1;
while (true) {
try {
List<RecruitShortVideoBO> videoList = recruitShortVideoDAO.pagingListNearlyByModifyTime(pageNo, fromTime);
if (CollectionUtils.isEmpty(videoList)) {
break;
}
sendUpdateUrlEvent(videoList);
pageNo = pageNo + 1;
} catch (Exception e) {
logger.error("异常:" + e);
break;
}
}
}
我们通过对比如上两个方法syncVideoToDp(List
和syncNearlyVideoToDp(Instant fromTime)
可以发现整体代码业务逻辑很相似,但是呢代码很冗余,为什么呢,因为整体方法业务逻辑逻辑很相似。
sendUpdateUrlEvent
处理数据。这时候,我们思考一下,无非区别在于查询的数据集不同,所以我们可以把代码优化如下。
通过提取一个成员方法,不关注数据集,仅业务逻辑处理。
/**
* 分页循环执行处理
* @param caller
*/
void doHandlePagingLoop(Function<Integer,List<RecruitShortVideoBO>> caller){
int pageNo = 1;
while (true) {
try {
List<RecruitShortVideoBO> videoList = caller.apply(pageNo);
if (CollectionUtils.isEmpty(videoList)) {
break;
}
sendUpdateUrlEvent(videoList);
pageNo = pageNo + 1;
} catch (Exception e) {
logger.error("异常:" + e);
break;
}
}
}
上述这个方法,方法参数就是一个Function接口,该接口有个特点,入参是Integer类型,返回参数是List。因为每次循环的时候,pageNo+1,这时候,需要回调把pageNo传给调用方,查询下一页的数据集。
方法一的代码优化如下:
public void syncVideoToDp(List<Long> ids) {
doHandlePagingLoop(pageNo -> recruitShortVideoDAO.listAllLiveVideo(ids, pageNo));
}
方法二的代码优化如下:
public void syncNearlyVideoToDp(Instant fromTime) {
doHandlePagingLoop(pageNo -> {
List<RecruitShortVideoBO> list = recruitShortVideoDAO.pagingListNearlyByModifyTime(pageNo, fromTime);
logger.info("[syncNearlyVideoToDp],本次处理数据条数={},pageNo={}", list.size(), pageNo);
return list;
});
}
通过对比如上方法一和方法二,我们发现代码经过了精简优雅了许多。本质上而言,就是提取了相同的业务逻辑代码,使得该方法中doHandlePagingLoop
不关注数据集是委托dao的某个方法,我只需要调用方给我数据集即可;其次呢,既然dao需要分页查询,新方法中则序号处理每页的数据后,pageNo+1,然后重新给调用法,查询下一页数据即可。
我们有没有发现jdk8中的stream api,本质上就是基于函数式编程,使得我么只需要传入一个lambda表达式即可。
譬如 forEach
就是一个Consumer
接口,譬如filter
就是个 Predicate
接口,譬如map
就是一个Function
接口,我们通过查看java.util.stream
的源码如下。
/**
* @since 1.8
*/
public interface Stream<T> extends BaseStream<T, Stream<T>> {
Stream<T> filter(Predicate<? super T> predicate);
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
Stream<T> peek(Consumer<? super T> action);
void forEach(Consumer<? super T> action);
}
所以我们调用Stream的forEach方法,本身就是把迭代的元素返回给调用方,所以源码中Consumer super T> 就是具体的泛型 ,如果我循环的是集合List 那么得到的元素也就是 Student;如果我们调用的map方法,得到的回调元素也是Student,这就是lambda(函数式)的魅力。