
在数据处理中,我们经常会遇到需要根据某个共同属性对数据记录进行分组和汇总的场景。例如,给定一个entities对象列表,每个对象包含一个开始日期(start_dt)、一个结束日期(stop_dt)和一个组编号(groupnum)。如果多个entities对象共享相同的groupnum,它们就属于同一个逻辑组。我们的目标是聚合这些对象,为每个组生成一个新的entities对象,其中新对象的start_dt是该组所有原始对象中最早的开始日期,而stop_dt是该组所有原始对象中最晚的结束日期。
原始数据结构示例:
| Start | Stop | GroupNum |
|---|---|---|
| 2018-11-13 | 2019-01-13 | 1 |
| 2019-01-14 | 2019-03-06 | 1 |
| 2019-03-07 | 2019-11-18 | 1 |
| 2020-08-23 | 2020-08-23 | 2 |
| 2021-11-19 | 2022-12-23 | 2 |
期望的聚合结果:
| Start | Stop | GroupNum |
|---|---|---|
| 2018-11-13 | 2019-11-18 | 1 |
| 2020-08-23 | 2022-12-23 | 2 |
Java 8引入的Stream API为处理集合数据提供了强大而灵活的工具。我们可以利用Collectors.groupingBy方法进行分组,然后对每个组进行进一步的处理以提取所需的聚合信息。
首先,我们需要定义Entities类,它包含start_dt、stop_dt和groupNum字段,以及相应的构造函数、getter方法和toString方法,以便于创建实例和打印输出。
立即学习“Java免费学习笔记(深入)”;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
public class Entities {
private final Date start_dt;
private final Date stop_dt;
private final int groupNum;
// 构造函数,接受字符串日期并解析
public Entities(String start_dt_str, String stop_dt_str, int groupNum) throws ParseException {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
this.start_dt = formatter.parse(start_dt_str);
this.stop_dt = formatter.parse(stop_dt_str);
this.groupNum = groupNum;
}
// 构造函数,接受Date对象
public Entities(Date start_dt, Date stop_dt, int groupNum) {
this.start_dt = start_dt;
this.stop_dt = stop_dt;
this.groupNum = groupNum;
}
// Getter方法
public Date getStart_dt() {
return start_dt;
}
public Date getStop_dt() {
return stop_dt;
}
public int getGroupNum() {
return groupNum;
}
@Override
public String toString() {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
return "Entities [start_dt=" + formatter.format(start_dt) +
", stop_dt=" + formatter.format(stop_dt) +
", groupNum=" + groupNum + "]";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entities entities = (Entities) o;
return groupNum == entities.groupNum &&
Objects.equals(start_dt, entities.start_dt) &&
Objects.equals(stop_dt, entities.stop_dt);
}
@Override
public int hashCode() {
return Objects.hash(start_dt, stop_dt, groupNum);
}
}聚合过程主要分为以下几个步骤:
import java.text.ParseException;
import java.util.List;
import java.util.stream.Collectors;
public class AggregationExample {
public static void main(String[] args) throws ParseException {
// 1. 创建初始列表
List<Entities> baseList = List.of(
new Entities("2018-11-13", "2019-01-13", 1),
new Entities("2019-01-14", "2019-03-06", 1),
new Entities("2019-03-07", "2019-11-18", 1),
new Entities("2020-08-23", "2020-08-23", 2),
new Entities("2021-11-19", "2022-12-23", 2));
// 2. 使用Stream API进行聚合
List<Entities> result = baseList.stream()
// 2.1. 按groupNum分组,结果为Map<Integer, List<Entities>>
.collect(Collectors.groupingBy(Entities::getGroupNum))
// 2.2. 将Map转换为Stream<Map.Entry<Integer, List<Entities>>>
.entrySet().stream()
// 2.3. 对每个分组进行映射,生成新的Entities对象
.map(entry -> {
List<Entities> groupEntities = entry.getValue();
// 2.4. 提取最早的start_dt(列表第一个元素的start_dt)
// 2.5. 提取最晚的stop_dt(列表最后一个元素的stop_dt)
// 注意:这里假设groupEntities列表内部已按日期排序
return new Entities(
groupEntities.get(0).getStart_dt(), // 获取组内第一个元素的开始日期
groupEntities.get(groupEntities.size() - 1).getStop_dt(), // 获取组内最后一个元素的结束日期
entry.getKey() // 获取当前组的groupNum
);
})
// 2.6. 将结果收集为List<Entities>
.toList(); // Java 16+,等同于.collect(Collectors.toList())
// 打印聚合结果
result.forEach(System.out::println);
}
}输出:
Entities [start_dt=2018-11-13, stop_dt=2019-11-18, groupNum=1] Entities [start_dt=2020-08-23, stop_dt=2022-12-23, groupNum=2]
输入列表的排序假设: 上述解决方案中,map操作直接通过groupEntities.get(0).getStart_dt()和groupEntities.get(groupEntities.size() - 1).getStop_dt()来获取最早和最晚日期。这强烈依赖于原始baseList在分组前已经按照groupNum和日期(start_dt或stop_dt)进行了排序。如果baseList的元素顺序是随机的,get(0)和get(size - 1)可能无法正确地提供最早和最晚的日期。
更健壮的方法:如果无法保证输入列表的排序,应在map操作内部使用min和max收集器来查找日期:
.map(entry -> {
List<Entities> groupEntities = entry.getValue();
Date minStartDate = groupEntities.stream()
.map(Entities::getStart_dt)
.min(Date::compareTo)
.orElse(null); // 处理空组情况
Date maxStopDate = groupEntities.stream()
.map(Entities::getStop_dt)
.max(Date::compareTo)
.orElse(null); // 处理空组情况
return new Entities(minStartDate, maxStopDate, entry.getKey());
})这种方式虽然代码量稍多,但更加健壮,不依赖于原始列表的内部排序。
日期处理: 示例中使用了java.util.Date和java.text.SimpleDateFormat进行日期解析和格式化。java.util.Date是可变对象,且设计上存在一些问题。在现代Java应用中,强烈推荐使用java.time包(即JSR 310)中的LocalDate、LocalDateTime等类,它们提供了更好的API设计、线程安全性和不可变性。
异常处理: 在Entities的构造函数中,SimpleDateFormat.parse()方法会抛出ParseException。在实际应用中,应妥善处理此异常,例如通过try-catch块捕获并记录错误,或将其转换为运行时异常。
通过Java Stream API,我们可以以声明式的方式优雅地解决基于共享属性进行数据分组和聚合的问题。Collectors.groupingBy是实现这一目标的关键,它将复杂的数据转换过程分解为易于理解和维护的步骤。在实际应用中,务必考虑数据源的特性(如是否已排序)以及选择合适的日期处理API,以确保解决方案的健壮性和准确性。
以上就是Java Stream API:按共享属性聚合对象并合并日期范围的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号