【发布时间】:2018-06-20 01:19:17
【问题描述】:
考虑这个类:-
import java.sql.Timestamp;
public class Report {
private short value;
private Timestamp created;
//Getters, Setters
}
我有一个List 使用ORDER BY created DESC 从数据库获得的报告。
任务是获取每个月的最新报告。我知道它可以在 SQL 级别完成,但由于某种原因我需要在 Java 中完成。
我就是这样解决的:-
/**
* Assuming that the reports are sorted with <code>ORDER BY created DESC</code>, this method filters the list so
* that it contains only the latest report for any month.
*
* @param reports Sorted list of reports
* @return List containing not more than one report per month
*/
public static List<Report> oneReportPerMonthFilter(List<Report> reports) {
Map<String, Report> monthlyReports = new HashMap<>();
reports.forEach(report -> {
String yearMonth = getCreatedYearMonth(report);
if (!monthlyReports.containsKey(yearMonth)) {
monthlyReports.put(yearMonth, report);
}
});
return new ArrayList<>(monthlyReports.values());
}
private static String getCreatedYearMonth(Report report) {
return YearMonth
.from(ZonedDateTime.of(report.getCreated().toLocalDateTime(), ZoneOffset.UTC))
.toString();
}
问题 1
虽然按预期工作,但我必须创建一个Map,然后将values 转换回List。使用 Java 8 Stream API 有更好的方法吗?也许是更“实用”的方式?
问题 2
将Timestamp 转换为YearMonth 的方法getCreatedYearMonth(Report report) 可以简化吗?目前它将Timestamp 更改为LocalDateTime,然后更改为ZonedDateTime,然后更改为YearMonth。
单元测试:-
@Test
public void shouldFilterOutMultipleReportsPerMonth() {
Report report1 = new Report();
report1.setCreated(Timestamp.from(Instant.EPOCH));
report1.setValue((short) 100);
Report report2 = new Report();
report2.setCreated(Timestamp.from(Instant.EPOCH.plus(10, ChronoUnit.DAYS)));
report2.setValue((short) 200);
Report report3 = new Report();
report3.setCreated(Timestamp.from(Instant.EPOCH.plus(40, ChronoUnit.DAYS)));
report3.setValue((short) 300);
List<Report> reports = Stream.of(report3, report2, report1).collect(Collectors.toList());
List<Report> filteredReportList = ExampleClass.oneReportPerMonthFilter(reports);
Assert.assertEquals(2, filteredReportList.size());
Assert.assertEquals((short) 300, (short) filteredReportList.get(0).getValue());
Assert.assertEquals((short) 200, (short) filteredReportList.get(1).getValue());
}
编辑 1
回答
感谢大家的回答。使用 Amith 和 Johannes 的答案,我想出了这个简单易读的版本:-
public static List<Report> oneReportPerMonthFilter(List<Report> reports) {
Set<YearMonth> found = new HashSet<>();
return reports.stream()
.filter(r -> found.add(getCreatedYearMonth(r)))
.collect(Collectors.toList());
}
private static YearMonth getCreatedYearMonth(Report report) {
return YearMonth.from(
report.getCreated()
.toInstant()
.atZone(ZoneOffset.UTC));
}
似乎没有快速将时间戳转换为年月的方法。我们可以从 Timestamp 中获取年月的字符串表示形式,如 Amith 所示。
【问题讨论】:
-
对于此类问题,提供MCVE(最小、完整且可验证的示例)会很有帮助,这样我们就可以自己运行代码并验证我们的答案是否正确。
-
@Radiodef 我已经提供了模型类(除了可以生成的 getter/setter)、有问题的方法以及可以运行以测试您的解决方案的单元测试。请告知我还应该添加什么。
-
为了使代码编译和运行而无需付出太多努力所需的一切。如果您不这样做,我认为您的问题不会被关闭或在这种情况下发生任何事情。这只是一个建议,让您更有可能获得更好的答案。
-
YearMonth可以直接从LocalDateTime创建。中间不需要转换成ZonedDateTime。您可以使用Arrays.asList()获得Report的列表。
标签: java java-8 functional-programming java-stream