如何使用Java 8 Stream按周分组LocalDate-Integer映射并获取每周最大值(含异常问题咨询)
解决Java Stream按周分组并取每周最大值的问题
我来帮你搞定这个按周分组取最大值的需求,先拆解下你遇到的几个痛点,再给出可行的解决方案,顺便解释下你原来代码出错的原因。
为什么你的代码会报错?
你原来的代码里用了w/M/YY来格式化日期,然后再解析回LocalDate,这里的核心问题是**YY代表的是“基于周的年份”(Week-based Year),而M是实际的月份**,这两个字段经常会出现冲突:比如某个日期属于2020年的第5周,但实际月份是2月,而基于周的年份第5周对应的月份可能是1月,解析器就会抛出你看到的月份不匹配异常。这种通过字符串格式化再解析的方式既麻烦又容易出错,完全没必要。
正确的解决方案思路
要解决所有痛点,关键是要生成唯一且可排序的周标识:
- 不能只用周数,必须带上年份(或者基于周的年份),避免不同年份的周混淆;
- 分组键要支持自然排序,方便TreeMap自动排序;
- 尽量直接操作LocalDate,避免字符串解析的麻烦。
下面给你两种实用的实现方案:
方案1:用每周的起始日期作为分组键(推荐)
把每个日期映射到该周的起始日期(比如周一,你也可以改成周日),用这个起始日期作为分组键。这样既直观,又能利用LocalDate的可排序性让TreeMap自动排序,还完全避免了字符串解析的问题。
import java.time.DayOfWeek; import java.time.LocalDate; import java.util.Map; import java.util.TreeMap; import java.util.stream.Collectors; public class WeekGroupingExample { // 获取某日期所在周的周一作为起始日期(可根据需求改成周日) private static LocalDate getWeekStartDate(LocalDate date) { return date.with(DayOfWeek.MONDAY); } public static Map<LocalDate, Integer> maxByWeek(Map<LocalDate, Integer> dateValueMap) { return dateValueMap.entrySet().stream() .collect(Collectors.groupingBy( // 按每周起始日期分组 entry -> getWeekStartDate(entry.getKey()), // 用TreeMap保证按键自然排序 TreeMap::new, // 计算每组的最大值 Collectors.reducing(0, Map.Entry::getValue, Math::max) )); } // 测试你的数据集 public static void main(String[] args) { // 你的数据集初始化(保持你原有的代码即可) Map<LocalDate,Integer> cumulativeGarbageWasteDate = ...; Map<LocalDate, Integer> result = maxByWeek(cumulativeGarbageWasteDate); result.forEach((weekStart, maxValue) -> System.out.printf("周起始日期:%s,最大值:%d%n", weekStart, maxValue)); } }
这个方案完美解决你的所有痛点:
- 不需要将字符串转回LocalDate,直接用原键的LocalDate生成分组键;
- TreeMap可以直接对LocalDate进行自然排序;
- 每周起始日期包含完整的年份和日期,不会出现跨年份周重叠的问题;
- 不同年份的周会被清晰区分(比如2020年第18周和2021年第18周的起始日期完全不同)。
方案2:用“年份-周数”字符串作为分组键
如果你更倾向于用类似2020-W05这样的字符串标识一周,也可以用基于周的年份+周数来生成键,TreeMap同样能对这种字符串进行自然排序:
import java.time.LocalDate; import java.time.temporal.IsoFields; import java.util.Map; import java.util.TreeMap; import java.util.stream.Collectors; public class WeekGroupingExample { public static Map<String, Integer> maxByWeekUsingYearWeekString(Map<LocalDate, Integer> dateValueMap) { return dateValueMap.entrySet().stream() .collect(Collectors.groupingBy( entry -> { LocalDate date = entry.getKey(); // 获取基于周的年份(避免跨年周的年份混乱) int weekYear = date.get(IsoFields.WEEK_BASED_YEAR); // 获取该年份下的周数 int weekOfYear = date.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR); // 格式化为"YYYY-WWW"的字符串,比如2020-W05 return String.format("%d-W%02d", weekYear, weekOfYear); }, TreeMap::new, Collectors.reducing(0, Map.Entry::getValue, Math::max) )); } }
这个方案的优势是键的可读性更强,直接能看到是哪一年的第几周,同样能解决所有痛点。
验证你的测试数据
用你的数据集测试的话,比如2021年1月1日-3日,如果周起始定义为周一,这几天会属于2020年的第53周(2020年12月28日是周一),而1月6日周三属于2021年第2周,分组会正确区分,最大值也会被正确计算。
内容的提问来源于stack exchange,提问作者dancisdoted d
相关产品推荐
相关产品推荐

