有时候用sql做自联接或者groupBy不方便,可以选择java的流式计算
两种方案
1:
// 身体肌肉率 MuscleList<KpiVO> lastMuscle = allDevicesVOS.stream().filter(device -> StringUtil.isNotBlank(device.getMuscle())) // 过滤出身体肌肉率都不为 null 的记录.collect(Collectors.groupingBy(device -> device.getCheckTime().toLocalDate())) // 按 LocalDate 分组.values().stream().map(devices -> devices.stream().max((d1, d2) -> d1.getCheckTime().compareTo(d2.getCheckTime())) // 找到每组最新的记录.orElse(null)) // 如果某组为空,返回 null.filter(device -> device != null) // 过滤掉 null 值.map(device -> {KpiVO kpiVO = new KpiVO();kpiVO.setCheckTimes(device.getCheckTime().toLocalDate().toString());kpiVO.setLast(device.getMuscle());return kpiVO;}).sorted((k1, k2) -> k1.getCheckTimes().compareTo(k2.getCheckTimes())) // 按 checkTimes 正序排列.collect(Collectors.toList());// 转换回 List<KpiVO>
2:
// 使用 Java 8 Stream API 处理结果List<LocalDate> result = checkTimes.stream().collect(Collectors.groupingBy(e -> e.getCheckTime().toLocalDate(), // 按日期分组Collectors.maxBy((e1, e2) -> e1.getCheckTime().compareTo(e2.getCheckTime())) // 选择每个日期中时间最晚的记录)).values().stream().map(Optional::get).sorted(Comparator.comparing(NormBasicDeviceDataEntity::getCheckTime).reversed()) // 对日期进行降序排序.map( entity -> {return entity.getCheckTime().toLocalDate();}).collect(Collectors.toList());
两种方案的区别在于
两段代码中都有groupingBy
操作,特别是第二段如何使用maxBy
来选择每个日期中的“最晚记录”。
第二段代码解析
List<LocalDate> result = checkTimes.stream() .collect(Collectors.groupingBy(e -> e.getCheckTime().toLocalDate(), // 按日期分组 Collectors.maxBy((e1, e2) -> e1.getCheckTime().compareTo(e2.getCheckTime())) // 选择每个日期中时间最晚的记录 )) .values() .stream() .map(Optional::get) .sorted(Comparator.comparing(NormBasicDeviceDataEntity::getCheckTime).reversed()) // 对日期进行降序排序 .map(entity -> { return entity.getCheckTime().toLocalDate(); }) .collect(Collectors.toList());
这段代码的核心是在分组操作中使用了maxBy
,让我们一步一步来分析。
1. groupingBy
+ maxBy
Collectors.groupingBy(e -> e.getCheckTime().toLocalDate(), Collectors.maxBy((e1, e2) -> e1.getCheckTime().compareTo(e2.getCheckTime())))
-
分组依据: 这部分和前面相同,
e -> e.getCheckTime().toLocalDate()
是按每个元素(checkTime
)的日期部分(LocalDate
)进行分组。 -
maxBy
: 关键的区别在于Collectors.maxBy
,它并不是简单地将所有元素放入一个集合中,而是用于选择每个分组中“最大”的元素,这个“最大”是根据你给定的比较器来确定的。maxBy((e1, e2) -> e1.getCheckTime().compareTo(e2.getCheckTime()))
表示按时间(checkTime
)比较,并选择每个分组中时间最晚的那个元素。- 由于是按时间升序排序(
compareTo
方法的返回值),因此maxBy
返回的将是每个日期中最新的记录。
结果会是一个
Map<LocalDate, Optional<NormBasicDeviceDataEntity>>
,其中每个键(日期)对应一个Optional<NormBasicDeviceDataEntity>
,表示每个日期的最新设备数据记录。
2. .values().stream()
values()
会返回一个Collection<Optional<NormBasicDeviceDataEntity>>
,这个集合是按日期分组后,每个日期组内最新的NormBasicDeviceDataEntity
对象。- 通过
.stream()
转化为流,可以继续后续操作。
3. .map(Optional::get)
Optional::get
会从每个Optional<NormBasicDeviceDataEntity>
中提取出实际的NormBasicDeviceDataEntity
对象(假设每个Optional
不为空)。
4. .sorted(Comparator.comparing(NormBasicDeviceDataEntity::getCheckTime).reversed())
- 按照每个
NormBasicDeviceDataEntity
的checkTime
降序排序(reversed()
),确保结果是按照时间从最新到最旧排列。
5. .map(entity -> entity.getCheckTime().toLocalDate())
- 最后将每个
NormBasicDeviceDataEntity
的checkTime
转换为LocalDate
,即提取出日期部分。
6. collect(Collectors.toList())
- 最终,结果被收集到一个
List<LocalDate>
中。
主要区别:groupingBy
与maxBy
的结合 vs. 单纯的groupingBy
groupingBy
+maxBy
:- 在每个分组内使用
maxBy
来找到最大(最新)的记录。这意味着对于每个日期,我们只保留时间最晚的记录,而不是所有记录。 - 结果是一个
Map<LocalDate, Optional<NormBasicDeviceDataEntity>>
,每个日期对应一个Optional
,表示日期下的最新设备数据。
- 在每个分组内使用
- 单纯的
groupingBy
:- 单纯的
groupingBy
只会将数据根据某个条件分组,不会对每组数据做任何筛选或操作。因此,它返回的是Map<LocalDate, List<Device>>
,其中每个日期对应一个包含该日期所有设备的列表。 - 没有
maxBy
的情况下,每个日期下的所有记录都会保留,并没有进行筛选。
- 单纯的
总结对比
- 第一种方法(使用
groupingBy
后再用max
)对每个分组中的数据进行了筛选,选出每个日期下的最新记录。 - 第二种方法(使用
groupingBy
和maxBy
)使用maxBy
直接在分组时就筛选出每个日期组中的最新记录,避免了后续的手动筛选。