调用getTime会改变Calendar值?求解释该异常日期计算行为
哇,这个问题戳中了Java老版Calendar类最反直觉的坑之一!让我给你掰扯清楚背后的原因:
核心根源:Calendar的延迟计算机制
Java的Calendar类采用**延迟计算(Lazy Evaluation)**设计:当你调用set()修改字段时,它不会立刻更新内部的时间戳,只是标记字段为“已修改”;只有当你调用getTime()、getTimeInMillis()这类需要获取实际时间的方法时,才会重新计算并同步所有日期字段。
结合Locale.GERMANY的周规则(一周从周一开始,而不是周日),两个示例的差异就源于这个机制:
示例1为什么会跳转到2018年?
看这段代码:
Calendar calendar = Calendar.getInstance(Locale.GERMANY); calendar.set(2017, 11, 11); // 注意:Calendar月份是0基,11代表12月,即2017-12-11 calendar.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY); System.out.println(calendar.getTime().toString());
你连续调用了两次set(),但没有触发计算。此时Calendar内部只记录了两个修改:DATE=11和DAY_OF_WEEK=SUNDAY,但这两个字段是冲突的(2017-12-11实际是周一)。
当最后调用getTime()触发计算时,Calendar需要解决这个冲突。由于延迟计算导致的字段不同步,它没有正确识别2017-12-11所在的周(周一到周日:12-11至12-17),反而因为Locale.GERMANY的周规则和字段优先级处理逻辑,错误地将日期调整到了下一年的第一个符合条件的周日——2018年1月7日。
示例2为什么正确?
Calendar calendar2 = Calendar.getInstance(Locale.GERMANY); calendar2.set(2017, 11, 11); calendar2.getTime(); // 关键!触发计算,同步所有字段 calendar2.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY); System.out.println(calendar2.getTime().toString());
调用getTime()后,Calendar已经完成了计算,明确了2017-12-11是周一,属于12月11日到17日的周。此时再设置DAY_OF_WEEK=SUNDAY,它就会精准调整到该周的最后一天(周日,12-17)——因为所有字段已经同步,没有冲突。
这是预期行为吗?
严格来说,这是Calendar类文档中明确的行为,但完全不符合直观预期。Calendar的延迟计算和字段冲突处理逻辑一直是Java日期API的历史遗留问题,这也是Java 8引入全新java.time API(比如LocalDate、WeekFields)的重要原因之一。
如果用Java 8+的API实现你的需求,代码会更清晰且无歧义:
import java.time.LocalDate; import java.time.temporal.TemporalAdjusters; import java.util.Locale; public class Main { public static void main(String[] args) { LocalDate date = LocalDate.of(2017, 12, 11); // 按德国周规则(周一为周起始),获取所在周的周日 LocalDate sunday = date.with(TemporalAdjusters.nextOrSame(java.time.DayOfWeek.SUNDAY)); System.out.println(sunday); // 输出:2017-12-17 } }
内容的提问来源于stack exchange,提问作者mzl

