(很抱歉编辑,我希望这更具可读性,但当我最初写答案时无法正确......现在是文章长度,但你去......)
只是为了补充已经说过的内容,问题出在返回的Calendar
实例的准备方式不同。我个人觉得这是一个设计缺陷,但可能有充分的理由。
当您调用 时,它会使用默认构造函数Calendar.getInstance()
创建一个新的。GregorianCalendar
此构造函数setCurrentTimeMillis(time)
以当前系统时间调用,然后调用受保护的方法complete()
。
但是,当您GregorianCalendar
使用您所做的构造函数创建一个 new 时,complete()
它永远不会被调用;相反,除其他外,只set(field, value)
需要提供的各种信息。这一切都很好,但它有一些令人困惑的后果。
当complete()
在第一种情况下调用时,会检查提到的成员变量dustmachine以确定应该重新计算哪些信息。这会产生一个强制计算所有字段(DAY
、WEEK_OF_MONTH
等)的分支。注意Calendar
确实是懒惰的;碰巧使用这种实例化方法会在现场强制进行显式重新计算(或在这种情况下为初始计算)。
那么,这有什么影响呢?鉴于在第二个对象创建的情况下没有执行前期场计算,这两个对象具有截然不同的状态。第一个填充了所有字段信息,而第二个仅包含您提供的信息。当您调用各种get*()
方法时,这无关紧要,因为任何更改都会在您检索信息时引发延迟重新计算步骤。然而,这种重新计算发生的顺序暴露了两种不同初始状态之间的差异。
在您的特定情况下,这是由于 中的以下相关代码computeTime()
,当您请求它时,必须调用它来计算正确的时间getTime()
:
boolean weekMonthSet = isSet[WEEK_OF_MONTH] || isSet[DAY_OF_WEEK_IN_MONTH];
...
boolean useDate = isSet[DATE];
if (useDate && (lastDateFieldSet == DAY_OF_WEEK
|| lastDateFieldSet == WEEK_OF_MONTH
|| lastDateFieldSet == DAY_OF_WEEK_IN_MONTH)) {
useDate = !(isSet[DAY_OF_WEEK] && weekMonthSet);
}
在第一种情况下,所有字段都因初始计算而设置。这允许weekMonthSet
为真,与DAY_OF_WEEK
您在调用中提供的set(field, value)
设置一起导致useDate
为假。
但是,在第二种情况下,由于没有计算任何字段,因此唯一设置的字段是您在构造函数和后续set(field, value)
调用中提供的字段。因此,useDate
将保持isSet[DATE]
为真,因为根据您的构造函数为真,但weekMonthSet
为假,因为对象中的其他字段尚未在任何地方计算,也未由您设置。
当useDate
为真时,正如暗示的那样,它使用您的日期信息来生成时间值。当useDate
为假时,它能够使用您的DAY_OF_WEEK
信息来计算您期望的时间,从而产生您所看到的差异。
最后,这提出了为什么在调用getTimeInMillis()
之前调用getTime()
会修复意外行为的问题。事实证明,由于您在两个对象中的调用,这些字段将被重新计算。无论出于何种(可能是真实的)原因,set(field, value)
这恰好发生在计算时间之后。因此,强制将时间在第二次计算一次Calendar
将基本上对齐两个对象的状态。在那之后,我相信对这两个对象的调用都get*()
应该一致。
理想情况下,您在第二种情况下使用的构造函数应该以一致性的名义执行此初始计算步骤(尽管可能出于性能原因,这不是首选),但事实并非如此,这就是您得到的。
所以,简而言之,正如其他人提到的,JodaTime是你的朋友,显然这些课程不那么重要。:)