前言

时隔三个月,我终于回归到技术博客写作中了。这段时间我经历了一个半月的应届生新员工培训,之后接续实习期间完成的工作,继续进行业务需求的开发。由于开发时实在分不出精力撰写文章,博客也被闲置了将近3个月,在此期间博客仅部署了两次平台框架版本的例行升级。

言归正传,最近在业务逻辑编写工作中用到了不少Stream流和Lambda函数。使用不同的Stream操作、编写不同功能的Lambda函数,能够输出各式各样的数据结构,从而满足业务逻辑的需求。于是写下这篇文章以作总结。这篇文章中的每个代码片段都是我在实际开发中编写的,希望能对各位有些帮助。

Talk is cheap, show me the code.

经典案例

1
2
3
Set<Integer> alarmIdSet = realTimeData.entrySet().stream().filter(it -> Objects.nonNull(it.getValue()) && it.getValue() > 0)
.map(it -> Integer.valueOf(it.getKey()))
.collect(Collectors.toSet());

这段代码是Stream流非常常见的使用案例,把常见的flitermapcollect方法都用到了,最终将一个MapEntrySet转换成所需条件的KeySet

Stream流不太了解的同学,可以查阅与本文同一专题的《Java函数式编程学习笔记(二)》。该篇文章较为详细地介绍了Stream流的各种操作。

对象List转Map

1
2
Map<Long, Integer> preInspectionMap = preInspectionList.stream()
.collect(Collectors.toMap(PreInspectionDTO::getId, PreInspectionDTO::getDeviceType));

如果对Stream有了基本的了解,那么这段代码就一目了然了,它使用Collectors.toMap方法取了一个POJO对象的两个属性分别作为MapKeyValue,最终将对象List转换为Map

值得一提的是,当Collectors.toMap方法有三个入参时,除了前两个入参表示KeyValue的映射方法外,第三个入参需传入一个Lambda,用于定义当key发生冲突时的处理方式。(v1, v2) -> v2表示如果新插入的键值对的Key已存在,就用这个Key的新Value替换旧Value。下面这段代码就符合这种场景。

1
2
3
4
return apiResult.getData().stream()
.collect(Collectors.toMap(it -> it.getMonitoredLabel() + "_" + it.getMonitoredId(),
it -> it,
(v1, v2) -> v2));

Lambda不太了解的同学,可以查阅与本文同一专题的《Java函数式编程学习笔记(一)》。该篇文章简短地介绍了Java函数式编程的概念和Java 8的Lambda特性。

分组

Stream拥有强大的分组输出操作,使用Collectors.groupingBy方法传入不同Lambda即可实现不同功能的归类。这里归纳了三种,分别是归类为对象List归类并统计数量归类为对象属性List

归类为对象List

1
2
Map<Long, List<SystemEventStatisticsVo>> eventGroups = systemEventStatisticsVos.stream()
.collect(Collectors.groupingBy(SystemEventStatisticsVo::getLogtime));

这是最基本的归类功能,只需在groupingBy方法中传入POJO对象的一个属性的Getter方法引用,就能按这个属性分组输出Map,此时MapValue数据类型为对象List

归类并统计数量

基本案例

1
2
Map<Long, Long> preInspectionEventMap = eventList.stream().
collect(Collectors.groupingBy(SystemEventDTO::getObjectId, Collectors.counting()));

将方法引用替换为Lambda

1
2
Map<String, Long> hiddenDangerMap = hiddenDangerAccountList.stream()
.collect(Collectors.groupingBy(it -> roomIdName.get(it.getRoomId()), Collectors.counting()));

稍微复杂一点的Lambda,内容是条件运算

1
2
3
Map<Integer, Long> enterpriseGroups = enterpriseList.stream().collect(
Collectors.groupingBy(it -> Objects.isNull(it.getEnterprisefacilityinstallstatus()) ?
-1 : it.getEnterprisefacilityinstallstatus(), Collectors.counting()));

这三段代码都在groupingBy方法中传入了一个Collectors.counting,用于在分组的同时统计每组元素的数量。

归类为对象属性List

1
2
3
Map<Long, List<Long>> roomPreInspectionMap = preInspectionList.stream()
.collect(Collectors.groupingBy(PreInspectionDTO::getRoomId,
Collectors.mapping(PreInspectionDTO::getId, Collectors.toList())));

这段代码在groupingBy方法中传入了一个Collectors.mapping,将分组的功能改为输出对象的属性List,相当于在归类为对象List后将对象List额外做了一次Stream流的Map操作。

统计数量

Java中统计数量的逻辑可以使用for循环遍历集合来实现,也可以使用Streamreduce操作来实现。

使用merge

1
2
3
resultMap.merge(EquipmentTypeEnum.of(preInspectionTypeMap.get(it.getObjectId())), 1L, Long::sum);

resultMap.merge(ConfirmEventStatusEnum.of(it.getConfirmEventStatus()), 1L, Long::sum);

上面两段代码都是将Map中一个Key对应的Value作为计数器,用merge方法实现了累加功能,从而达到统计数量的效果。merge方法的三个入参分别是Key的取值;每次取到相同Key时累加的值;实现累加的Lambda或方法引用。

此处merge方法的含义为:每次取到相同的Key时,在这个Key对应的Value基础上加1。事实上merge不止有累加的功能,更多功能还等待着我们去发掘。

使用reduce

1
2
Integer eventCount = eventList.stream().map(SystemEventStatisticsVo::getCount)
.reduce(0, Integer::sum);

前面说到,reduce方法是Stream流的一种聚合操作,能将所有元素聚合成一个结果。这里是取了POJO对象的Count属性并做累加操作,得到所有元素Count属性的总和。

1
2
3
4
long eventCount = roomPreInspectionList.stream().reduce(0L, (total, it) -> {
long eventNum = Objects.isNull(preInspectionEventMap.get(it)) ? 0L : preInspectionEventMap.get(it);
return total + eventNum;
});

这段代码将一段Lambda作为聚合方法,按聚合函数的逻辑输出一个结果。

使用Optional

1
2
Optional<TrendDataVo> first = data.stream().filter(d -> paramsIsEquals(d, l)).findFirst();
first.ifPresent(trendDataVo -> loadCurveVo.setDataList(trendDataVo.getDataList()));

这段代码中,Optional的作用是在无法确定一个Stream的元素是否存在时定义的一种数据结构。当元素存在时,才执行ifPresent方法中的逻辑,是处理NPE或数组越界异常时替代if语句的更优雅的一种方法。


非常感谢你的阅读,辛苦了!


参考文章: (感谢以下资料提供的帮助)