解決Java Calendar類set()方法的陷阱
在項目中,需要獲取指定年份和月份的最后一天。我在網(wǎng)上找到了一個用Calendar類獲取的方法,代碼如下:
import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; public class TestCalendar { public static void main(String[] args) { String s = new SimpleDateFormat("yyyy-MM-dd") .format(getLastDay(2017, 9)); System.out.println(s); } public static Date getLastDay(int year, int month) { //獲取Calendar類的實例 Calendar c = Calendar.getInstance(); //設(shè)置年份 c.set(Calendar.YEAR, year); //設(shè)置月份,因為月份從0開始,所以用month - 1 c.set(Calendar.MONTH, month - 1); //獲取當(dāng)前時間下,該月的最大日期的數(shù)字 int lastDay = c.getActualMaximum(Calendar.DAY_OF_MONTH); //將獲取的最大日期數(shù)設(shè)置為Calendar實例的日期數(shù) c.set(Calendar.DAY_OF_MONTH, lastDay); return c.getTime(); } }
剛開始使用這個方法的時候,很正常。后來在10月31號(這個日期很重要)當(dāng)天測試的時候,傳遞的參數(shù)時2017年9月,即上面的代碼,但是結(jié)果卻出現(xiàn)的了問題,結(jié)果如下圖:
本來該是2017-09-30,可是結(jié)果卻是2017-10-01,我原先測試過,這個方法是沒有問題的,可是出了這樣的問題。后來我斷點測試,在剛獲取到Calendar實例的時候,實例中的字段值如下圖:
但是發(fā)現(xiàn)在執(zhí)行完
c.set(Calendar.MONTH, month - 1);
這行的代碼的時候,Calendar的實例中,MONTH字段的值不是我預(yù)想中的8(月份字段從0開始),而是9,而且DAY_OF_MONTH字段的值從31變成了1,如下圖所示:
因此,可以判斷Calendar實例獲取到的時候,是10月31號,實例中的DAY_OF_MONTH的值是31,當(dāng)把MONTH字段的值設(shè)置為8后,因為9月份只有30天,那DAY_OF_MONTH的值就多1,會自動向后順延1天,變成了2017-10-01 。
但是,還是有其他的問題,因為下面還執(zhí)行了
c.set(Calendar.DAY_OF_MONTH, lastDay);
這句代碼,最后的日期應(yīng)該是2017-10-31才對,但是run的結(jié)果卻是2017-10-01,debug的結(jié)果是2017-10-31 。
我第一感覺認(rèn)為Calendar類是不是存在線程安全問題,可是后來一想就覺得不對,畢竟我只是在主線程中運行,沒有多線程,并不存在這個問題。
第二天我又嘗試了下,發(fā)現(xiàn)了問題的原因,如上面的最后一張圖所示,在debug的過程中,我用IDEA的watches功能查看了Calendar實例的字段值,用了get()方法,如果我刪除掉這幾個get方法之后,發(fā)現(xiàn)run和debug的值是一樣的,都是2017-10-01,說明問題出在get()方法上。
因此,可以做如下修改:
在代碼中,直接打印變量c的值,可以發(fā)現(xiàn),在調(diào)用get()方法之前,變量c的各字段值是set()方法設(shè)置的,但是并沒有對其進(jìn)行驗證計算,在調(diào)用get()方法的過程中,會對各字段驗證計算。我查看了部分源碼,在調(diào)用get(),add(),getTime()等方法的過程中,底層都會調(diào)用computeTime()方法,對各字段的時間驗證計算。
另外,又做了一個demo測試,以佐證上面的結(jié)論,如下:
import java.text.SimpleDateFormat; import java.util.Calendar; public class TestCalendar2 { public static void main(String[] args) { Calendar c = Calendar.getInstance(); c.set(Calendar.MONTH, 8); //將月份設(shè)置為9月 c.set(Calendar.DAY_OF_MONTH, 32); //將日期設(shè)置為32 System.out.println(c); //直接打印Calendar實例,不使用getTime()方法 c.get(Calendar.MONTH); System.out.println(c); } }
結(jié)果如下:
即使設(shè)置的DAY_OF_MONTH值是明顯非法的,但是并不會在調(diào)用get()方法之前進(jìn)行計算進(jìn)位。
在查詢問題的過程中,也看到了其他的一些問題,下面對add(),set(),roll()方法的區(qū)別做了解釋:
示例代碼:
Calendar c = Calendar.getInstance(); c.set(2014, Calendar.MARCH, 31); c.add(Calendar.MONTH, 13); System.out.println(c.getTime());// 2015-04-30 c.set(2014, Calendar.MARCH, 31); c.set(Calendar.MONTH, c.get(Calendar.MONTH) + 13); System.out.println(c.getTime());// 2015-05-01 c.set(2014, Calendar.MARCH, 31); c.roll(Calendar.MONTH, 13); System.out.println(c.getTime());//2014-04-30
ADD方法
以調(diào)整的單位為基點(本例中為月),較大的單位(年)會發(fā)生借位、進(jìn)位。 較小的單位會往小調(diào)整。
本例中,2014-03-31,加上13個月,年份會進(jìn)位為2015年。 4月31日是不存在的,所以往小調(diào)整為4月30日。
比較典型的運用場景是,日歷的按月切換。
當(dāng)前日期為2014-03-31,點擊【下一月】按鈕時,日期會變成2014-04-30.
SET方法
所有的單位都會往大調(diào)整。
本例中,2014-03-31,加上13個月,年份會進(jìn)位為2015年。 4月31日是不存在的,所以往大調(diào)整為5月1日
ROLL方法
以調(diào)整的單位為基點(本例中為月),較大的單位(年)不會發(fā)生改變。 較小的單位會往小調(diào)整。
本例中,2014-03-31,加上13個月,年份依然為2014年。 4月31日是不存在的,所以往小調(diào)整為4月30日。
日會根據(jù)年、月來判斷出日的取值范圍,然后在1~31之間無限循環(huán)滾動,但并不會影響到年、月的值。
總結(jié)三點:
1、add() 有兩條規(guī)則:
a)當(dāng)被修改的字段超出它的取值范圍時,那么比它大的字段會自動修正。
b)如果比它小的字段是不可變的/不在取值范圍內(nèi)(由 Calendar 的實現(xiàn)類決定),那么該小字段會修正到變化最小的值。
2、Roll() 的規(guī)則只有第二條
當(dāng)被修改的字段超出它的取值范圍時,那么比它大的字段不會被修正。比它小的字段會修正到變化最小的值。
3、Set()
比被修改的字段大的字段會根據(jù)字段是增大還是減小自動改變大小,比被修改字段小的字段如果是不可變的/不在取值范圍內(nèi),會自動增大到變化最小的值。
回到最初的問題,獲取指定年份和月份的最大的日期的方法要怎么辦?
方法可以改為:
public static Date getLastDay(int year, int month) { Calendar c = Calendar.getInstance(); //獲取Calendar類的實例 c.clear(); c.set(Calendar.YEAR, year); //設(shè)置年份 c.set(Calendar.MONTH, month - 1); //設(shè)置月份,因為月份從0開始,所以用month - 1 int lastDay = c.getActualMaximum(Calendar.DAY_OF_MONTH); //獲取當(dāng)前時間下,該月的最大日期的數(shù)字 c.set(Calendar.DAY_OF_MONTH, lastDay); //將獲取的最大日期數(shù)設(shè)置為Calendar實例的日期數(shù) return c.getTime(); //返回日期 }
用clear()方法,將Calendar實例的字段和時間都設(shè)置為未定義,這樣可以解決這個問題。
當(dāng)然網(wǎng)上也有將月份設(shè)置為下個月,然后用add(Calendar.DAY_OF_MONTH, -1)這樣的方法也可以得到結(jié)果,不過這里就不詳細(xì)介紹了。
到此這篇關(guān)于解決Java Calendar類set()方法的陷阱的文章就介紹到這了,更多相關(guān)Java Calendar set()內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot+nginx+https+linux實現(xiàn)負(fù)載均衡加域名訪問簡單測試
這篇文章主要介紹了springboot+nginx+https+linux實現(xiàn)負(fù)載均衡加域名訪問簡單測試,本文通過實例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值 ,需要的朋友可以參考下2019-05-05java:try...catch跳過異常繼續(xù)處理循環(huán)問題
這篇文章主要介紹了java:try...catch跳過異常繼續(xù)處理循環(huán)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10Java實現(xiàn)平鋪列表(List)互轉(zhuǎn)樹形(Tree)結(jié)構(gòu)
本文主要介紹了Java實現(xiàn)平鋪列表(List)互轉(zhuǎn)樹形(Tree)結(jié)構(gòu),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08