(抱歉编辑,我希望这更具可读性,但当我最初写答案时无法正确...现在是文章长度,但你去... )
只是为了补充已经说过的内容,问题源于返回的Calendar 实例的准备方式不同。我个人觉得这是一个设计缺陷,但可能有充分的理由。
当您调用Calendar.getInstance() 时,它会使用默认构造函数创建一个新的GregorianCalendar。这个构造函数用当前系统时间调用setCurrentTimeMillis(time),然后调用受保护的方法complete()。
但是,当您使用您所做的构造函数创建新的GregorianCalendar 时,永远不会调用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 为真,这与您在对set(field, value) 的调用中提供的DAY_OF_WEEK 一起设置,导致useDate 为假。
但是,在第二种情况下,由于没有计算任何字段,因此唯一设置的字段是您在构造函数和随后的 set(field, value) 调用中提供的字段。因此,useDate 将保持为真,因为 isSet[DATE] 根据您的构造函数为真,但 weekMonthSet 为假,因为对象中的其他字段尚未在任何地方计算,也未由您设置。
当useDate 为真时,正如暗示的那样,它使用您的日期信息来生成时间值。当useDate 为假时,它可以使用您的DAY_OF_WEEK 信息来计算您预期的时间,从而产生您所看到的差异。
最后,这提出了一个问题,为什么在调用getTime() 之前调用getTimeInMillis() 会修复意外行为。事实证明,由于您在两个对象中调用set(field, value),字段将重新计算。无论出于何种(可能是真正的)原因,这恰好发生在计算时间之后。因此,强制在第二个Calendar 上计算一次时间将基本上对齐两个对象的状态。在那之后,我相信对 get*() 的调用应该对两个对象都有效。
理想情况下,您在第二种情况下使用的构造函数应该以一致性的名义执行此初始计算步骤(尽管可能出于性能原因,这不是首选),但事实并非如此,这就是您的得到。
所以,简而言之,正如其他人提到的,JodaTime 是你的朋友,显然这些课程不那么重要。 :)