2010-07-14 54 views
3

另一個奇怪的行爲,看看一段代碼波紋管:與GregorianCalendar的

Calendar today1 = Calendar.getInstance(); 
today1.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY); 
System.out.println(today1.getTime()); 

Calendar today2 = new GregorianCalendar(2010, Calendar.JULY, 14); 
today2.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY); 
System.out.println(today2.getTime()); 

我很迷茫......假設我今天跑它作爲2010年7月14日,輸出爲:

Fri Jul 16 14:23:23 PDT 2010 
Wed Jul 14 00:00:00 PDT 2010 

最煩人的是,如果我添加today2.getTimeInMillis()(或任何其他get()方法)它將產生一致的結果。對於代碼波紋管:

Calendar today2 = new GregorianCalendar(2010, Calendar.JULY, 14); 
today2.getTimeInMillis(); 
today2.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY); 
System.out.println(today2.getTime()); 

結果是:

Fri Jul 16 00:00:00 PDT 2010 

回答

4

答案在JavaDoc實際上是記錄了java.util.Calendar

這裏列出:

集(F,value)改變日曆域F價值。另外,它設置一個內部成員變量來指示日曆字段f已被更改。雖然字段f立即更改爲 ,但日曆的 毫秒不會被重新計算,直到 下一次調用get(),getTime()或 getTimeInMillis()爲止。

這樣解釋你所看到的行爲,但我與另一個響應你的問題,你應該考慮JodaTime如果你打算做很多日期編碼的同意。

+0

'日曆'確實延遲加載,但這不*實際*解釋問題。爲什麼它不適用於'新的GregorianCalendar(y,m,d)',但它可以與'set(y,m,d)'配合使用? – BalusC 2010-07-14 22:33:04

+0

是的,它的確解釋了。 這是因爲在GregorianCalendar(y,m,d)中,您還沒有計算WEEK_OF_YEAR。在set(y,m,d)中,您首先調用了Calendar.getInstance(),它爲我們提供了一個填充了所有字段的對象。 – 2010-08-17 18:12:01

2

你其實應該使用Calendar#getInstance()得到一個實例,而不是new GregorianCalendar()。將該行更換爲

Calendar today2 = Calendar.getInstance(); 
today2.set(2010, Calendar.JULY, 14); 

它會順利的。

對不起,沒有行爲的詳細解釋,預計Calendar以及java.util.Date是當前Java SE API中的主要史詩故障之一。如果您正在進行密集的日期/時間操作,那麼我建議您看看JodaTime。即將推出的新Java 7將搭載基於JodaTime的改進日期/時間API(JSR-310)。

0

(對不起,編輯時,我希望這個可讀性更強一些,但是當我最初寫出答案時,它不能正確無誤)現在它是散文長度,但是你去...)

只是添加到已經說過的內容中,問題出現在返回的Calendar實例中的準備不同。我個人覺得這是一個設計缺陷,但可能有很好的理由。

當您致電Calendar.getInstance()時,它會使用默認構造函數創建一個新的GregorianCalendar。此構造函數用當前系統時間調用setCurrentTimeMillis(time),然後調用受保護的方法complete()

但是,當您使用所做的構造函數創建新的GregorianCalendar時,將永遠不會調用complete();相反,除其他外,只有set(field, value)被稱爲提供的各種信息位。這一切都很好,但它有一些令人困惑的後果。

在第一種情況下調用complete()時,會檢查成員變量dustmachine以確定應重新計算哪些信息。這導致一個分支強制計算所有字段(DAY,WEEK_OF_MONTH等)。請注意0​​確實是懶惰的;它只是發生,使用這種實例化的方法強制當場重新計算(或在這種情況下,初始計算)。

那麼,這有什麼影響?鑑於在第二個對象創建的情況下沒有執行前期字段計算,這兩個對象的狀態差別很大。第一個信息填充了所有的字段信息,第二個信息只包含您提供的信息。當你調用各種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]對您的構造函數爲true,但weekMonthSet是錯誤的,因爲對象中的其他字段尚未在任何地方計算,也未由您設置。

useDate爲真時,正如暗示的那樣,它使用您的日期信息來生成該時間的值。當useDate爲假時,它可以使用您的DAY_OF_WEEK信息來計算您期望的時間,從而導致您看到的差異。

最後,這引出了爲什麼在致電getTime()之前致電getTimeInMillis()將解決意外行爲的問題。事實證明,字段將因您在兩個對象中的set(field, value)調用而重新計算。這恰好發生在之後的時間計算,對於任何(可能是真正的)原因。因此,強制時間在第二個Calendar上計算一次將基本上對齊兩個對象的狀態。在那之後,我相信對get*()的呼叫應該對兩個對象都一致地工作。

理想情況下,您在第二種情況下使用的構造函數應該以一致性名稱執行此初始計算步驟(儘管可能出於性能方面的原因,這不是首選),但它不是,這是你得到。

所以,總之,正如其他人提到的那樣,JodaTime是你的朋友,顯然這些類是不那麼重要。 :)