Java開發(fā)規(guī)范學(xué)習(xí)--日期和時(shí)間

近期眶痰,阿里巴巴發(fā)布了《Java開發(fā)手冊(cè)(泰山版)》瘤旨。

摘錄部分官網(wǎng)的說明

《Java開發(fā)手冊(cè)》始于阿里內(nèi)部規(guī)約,在全球Java開發(fā)者共同努力下竖伯,已成為業(yè)界普遍遵循的開發(fā)規(guī)范存哲。
手冊(cè)涵蓋編程規(guī)約、異常日志七婴、單元測(cè)試祟偷、安全規(guī)約、MySQL數(shù)據(jù)庫打厘、工程規(guī)約修肠、設(shè)計(jì)規(guī)約七大維度。
此次泰山版發(fā)布户盯,將帶來三大亮點(diǎn):新增5條日期時(shí)間規(guī)約嵌施;新增2條表別名sql規(guī)約;新增統(tǒng)一錯(cuò)誤碼規(guī)約莽鸭。

阿里在2016年12月7日首次向業(yè)界開放《Java開發(fā)手冊(cè)》以來吗伤,獲得了絕大部分開發(fā)者的認(rèn)可,目前已經(jīng)成為行業(yè)的事實(shí)標(biāo)準(zhǔn)硫眨。很多公司直接引用作為內(nèi)部的編碼規(guī)范標(biāo)準(zhǔn)足淆,或者在此基礎(chǔ)上再針對(duì)自己行業(yè)的情況來擴(kuò)展一些內(nèi)部規(guī)約來形成自己的編碼規(guī)范標(biāo)準(zhǔn)。在開發(fā)手冊(cè)的指導(dǎo)下,限制了過度的個(gè)性化巧号,以一個(gè)相對(duì)統(tǒng)一的標(biāo)準(zhǔn)族奢,提升了代碼的可讀性,降低了溝通成本裂逐。

現(xiàn)在一些公司在面試Java開發(fā)的時(shí)候歹鱼,會(huì)要求看候選人的github上的代碼泣栈,來考察候選人在編碼規(guī)范卜高、風(fēng)格方面是否能符合業(yè)界統(tǒng)一的規(guī)范,因?yàn)橐?guī)范的代碼會(huì)給團(tuán)隊(duì)帶來很多好處南片,邏輯清晰掺涛,閱讀友好,別人接手起來也易于維護(hù)和升級(jí)疼进。所以薪缆,作為開發(fā)者來說,學(xué)習(xí)最新版《Java開發(fā)手冊(cè)》伞广,不僅是對(duì)自身編碼能力的提升拣帽,對(duì)團(tuán)隊(duì)協(xié)作、適用這個(gè)不斷變化的開發(fā)生態(tài)也很重要嚼锄。

因?yàn)橹皩W(xué)習(xí)過之前的版本减拭,所以本次主要針對(duì)新增的內(nèi)容進(jìn)行一下學(xué)習(xí),也說說自己的理解区丑,今天先來說的是拧粪,日期和時(shí)間相關(guān)的內(nèi)容。

先來看一下《Java開發(fā)手冊(cè)(泰山版)》關(guān)于這塊的內(nèi)容

1. 【強(qiáng)制】日期格式化時(shí)沧侥,傳入 pattern 中表示年份統(tǒng)一使用小寫的 y可霎。

說明:日期格式化時(shí),yyyy 表示當(dāng)天所在的年宴杀,而大寫的 YYYY 代表是 week in which year(JDK7 之后引入的概念)癣朗,意思是當(dāng)天所在的周屬于的年份,一周從周日開始旺罢,周六結(jié)束旷余,只要本周跨年,返回的 YYYY就是下一年主经。

正例:

表示日期和時(shí)間的格式如下所示:

new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

2. 【強(qiáng)制】在日期格式中分清楚大寫的 M 和小寫的 m荣暮,大寫的 H 和小寫的 h 分別指代的意義。

說明:日期格式中的這兩對(duì)字母表意如下:1) 表示月份是大寫的 M罩驻; 2) 表示分鐘則是小寫的 m穗酥; 3) 24 小時(shí)制的是大寫的 H; 4) 12 小時(shí)制的則是小寫的 h。

3. 【強(qiáng)制】獲取當(dāng)前毫秒數(shù):System.currentTimeMillis(); 而不是 new Date().getTime()砾跃。

說明:如果想獲取更加精確的納秒級(jí)時(shí)間值骏啰,使用 System.nanoTime 的方式。在 JDK8 中抽高,針對(duì)統(tǒng)計(jì)時(shí)間等場(chǎng)景判耕,推薦使用 Instant 類。

4. 【強(qiáng)制】不允許在程序任何地方中使用:1)java.sql.Date 2)java.sql.Time 3)java.sql.Timestamp翘骂。

說明:第 1 個(gè)不記錄時(shí)間壁熄,getHours()拋出異常;第 2 個(gè)不記錄日期碳竟,getYear()拋出異常草丧;第 3 個(gè)在構(gòu)造方法 super((time/1000)*1000),fastTime 和 nanos 分開存儲(chǔ)秒和納秒信息莹桅。反例: java.util.Date.after(Date)進(jìn)行時(shí)間比較時(shí)昌执,當(dāng)入?yún)⑹?java.sql.Timestamp 時(shí),會(huì)觸發(fā) JDK BUG(JDK9 已修復(fù))诈泼,可能導(dǎo)致比較時(shí)的意外結(jié)果懂拾。

5. 【強(qiáng)制】不要在程序中寫死一年為 365 天,避免在公歷閏年時(shí)出現(xiàn)日期轉(zhuǎn)換錯(cuò)誤或程序邏輯錯(cuò)誤铐达。

正例:

// 獲取今年的天數(shù)

int daysOfThisYear = LocalDate.now().lengthOfYear();

// 獲取指定某年的天數(shù)

LocalDate.of(2011, 1, 1).lengthOfYear();

反例:

// 第一種情況:在閏年 366 天時(shí)岖赋,出現(xiàn)數(shù)組越界異常

int[] dayArray = new int[365];

// 第二種情況:一年有效期的會(huì)員制,今年 1 月 26 日注冊(cè)娶桦,硬編碼 365 返回的卻是 1 月 25 日

Calendar calendar = Calendar.getInstance();

calendar.set(2020, 1, 26);

calendar.add(Calendar.DATE, 365);

6. 【推薦】避免公歷閏年 2 月問題懈费。閏年的 2 月份有 29 天萌焰,一年后的那一天不可能是 2 月 29日。

7. 【推薦】使用枚舉值來指代月份。如果使用數(shù)字哮兰,注意 Date乎串,Calendar 等日期相關(guān)類的月份month 取值在 0-11 之間历恐。

說明:參考 JDK 原生注釋荸恕,Month value is 0-based. e.g., 0 for January.正例: Calendar.JANUARY,Calendar.FEBRUARY菩混,Calendar.MARCH 等來指代相應(yīng)月份來進(jìn)行傳參或比較忿墅。

我們一條一條來拆解。

1. 【強(qiáng)制】日期格式化時(shí)沮峡,傳入 pattern 中表示年份統(tǒng)一使用小寫的 y疚脐。

平時(shí)不注意小細(xì)節(jié)的同學(xué),可能已經(jīng)在2019-12-31遇到了這個(gè)問題了邢疙,先來看一下下面代碼:

Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, 2019);
calendar.set(Calendar.MONTH, Calendar.DECEMBER);
calendar.set(Calendar.DAY_OF_MONTH, 31);
SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat format2 = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
System.out.println("2019-12-31 用yyyy格式化的結(jié)果:" + format1.format(calendar.getTime()));
System.out.println("2019-12-31 用YYYY格式化的結(jié)果:" + format2.format(calendar.getTime()));

運(yùn)行結(jié)果:

2019-12-31 用yyyy格式化的結(jié)果:2019-12-31 19:24:55
2019-12-31 用YYYY格式化的結(jié)果:2020-12-31 19:24:55

可以看到棍弄,2019-12-31用YYYY格式化的結(jié)果是2020年望薄,不太符合我們的預(yù)期。原因是呼畸,YYYY格式化年份痕支,是按照這一周(按照國(guó)際慣例,一周從周日開始蛮原,到周六結(jié)束)所屬的年份確定的卧须,而如果這一周如果剛好跨年了,那么這一周所有的日期的年份格式化都會(huì)是新的一年儒陨。查詢?nèi)f年歷花嘶,看到下圖:


萬年歷20191231.png

可以看到,這一周是從2019-12-29至2020-01-04框全,所以29察绷、30、31這幾天用YYYY去格式化的時(shí)候津辩,都會(huì)顯示成2020。
說到這里容劳,其實(shí)不止yyyy和YYYY喘沿,mm和MM,dd和DD竭贩,大小寫所代表的含義都是不同的蚜印,整理了一份資料,可以參考

y 年 Year 1996; 96
M 年中的月份 Month July; Jul; 07
w 年中的周數(shù) Number 27
W 月份中的周數(shù) Number 2
D 年中的天數(shù) Number 189
d 月份中的天數(shù) Number 10
F 月份中的星期 Number 2
E 星期中的天數(shù) Text Tuesday; Tue
a Am/pm 標(biāo)記 Text PM
H 一天中的小時(shí)數(shù)e799bee5baa6e997aee7ad94e4b893e5b19e31333363383438(0-23) Number 0
k 一天中的小時(shí)數(shù)(1-24) Number 24
K am/pm 中的小時(shí)數(shù)(0-11) Number 0
h am/pm 中的小時(shí)數(shù)(1-12) Number 12
m 小時(shí)中的分鐘數(shù) Number 30
s 分鐘中的秒數(shù) Number 55
S 毫秒數(shù) Number 978
z 時(shí)區(qū) General time zone Pacific Standard Time; PST; GMT-08:00
Z 時(shí)區(qū) RFC 822 time zone -0800

順便說下留量,上面有1行代碼大家可能沒特別留意

calendar.set(Calendar.MONTH, Calendar.DECEMBER);

這里設(shè)置月份的時(shí)候窄赋,值的范圍是0-11,代表我們認(rèn)知中的1月-12月楼熄,最好直接使用Calendar.DECEMBER這樣的常量忆绰,點(diǎn)進(jìn)去也可以清晰看到常量的值和注釋,這樣代碼可讀性更高可岂,也更加健壯

/**
     * Value of the {@link #MONTH} field indicating the
     * twelfth month of the year in the Gregorian and Julian calendars.
     */
    public static final int DECEMBER = 11;

2. 【強(qiáng)制】在日期格式中分清楚大寫的 M 和小寫的 m错敢,大寫的 H 和小寫的 h 分別指代的意義。

這條在上一條里面講過了缕粹,不重復(fù)稚茅,可以參考上面的含義表。

3. 【強(qiáng)制】獲取當(dāng)前毫秒數(shù):System.currentTimeMillis(); 而不是 new Date().getTime()平斩。

這條我理解一方面是效率上的考慮亚享,一方面是不用new這個(gè)Date對(duì)象出來。有人會(huì)問绘面,為什么System.currentTimeMillis()效率更高呢欺税?其實(shí)我們看一下Date的getTime方法糜芳,一看便知

public long getTime() {
    return getTimeImpl();
}

private final long getTimeImpl() {
    if (cdate != null && !cdate.isNormalized()) {
        normalize();
    }
    return fastTime;
}

上面2個(gè)方法都是Date類里面的,可以看到getTime方法其實(shí)是返回了fastTime屬性的值魄衅,那么這個(gè)屬性的值是怎么來的呢峭竣?我們看下構(gòu)造方法

/**
 * Allocates a {@code Date} object and initializes it so that
 * it represents the time at which it was allocated, measured to the
 * nearest millisecond.
 *
 * @see     java.lang.System#currentTimeMillis()
 */
public Date() {
    this(System.currentTimeMillis());
}

/**
 * Allocates a {@code Date} object and initializes it to
 * represent the specified number of milliseconds since the
 * standard base time known as "the epoch", namely January 1,
 * 1970, 00:00:00 GMT.
 *
 * @param   date   the milliseconds since January 1, 1970, 00:00:00 GMT.
 * @see     java.lang.System#currentTimeMillis()
 */
public Date(long date) {
    fastTime = date;
}

這回就非常清晰了,這個(gè)fastTime的值其實(shí)是構(gòu)造Date對(duì)象的時(shí)候初始化的晃虫,當(dāng)我們調(diào)用無參的構(gòu)造方法來構(gòu)造Date對(duì)象的時(shí)候皆撩,其實(shí)是使用了System.currentTimeMillis()來作為入?yún)碚{(diào)用了有參的構(gòu)造方法,所以如果我們要獲取當(dāng)前時(shí)間戳哲银,System.currentTimeMillis()的效率肯定會(huì)比new Date().getTime()更高扛吞。
當(dāng)面說道這里,我們也注意到了說明里面的另外一段話

如果想獲取更加精確的納秒級(jí)時(shí)間值荆责,使用 System.nanoTime 的方式滥比。

那么,這個(gè)System.nanoTime是什么呢做院?

/**
* Returns the current value of the running Java Virtual Machine's
* high-resolution time source, in nanoseconds.
*
* This method can only be used to measure elapsed time and is
* not related to any other notion of system or wall-clock time.
* The value returned represents nanoseconds since some fixed but
* arbitrary <i>origin</i> time (perhaps in the future, so values
* may be negative).  The same origin is used by all invocations of
* this method in an instance of a Java virtual machine; other
* virtual machine instances are likely to use a different origin.
*
* <p>This method provides nanosecond precision, but not necessarily
* nanosecond resolution (that is, how frequently the value changes)
* - no guarantees are made except that the resolution is at least as
* good as that of {@link #currentTimeMillis()}.
*
* <p>Differences in successive calls that span greater than
* approximately 292 years (2<sup>63</sup> nanoseconds) will not
* correctly compute elapsed time due to numerical overflow.
*
* <p>The values returned by this method become meaningful only when
* the difference between two such values, obtained within the same
* instance of a Java virtual machine, is computed.
*
* <p>For example, to measure how long some code takes to execute:
* <pre> {@code
* long startTime = System.nanoTime();
* // ... the code being measured ...
* long elapsedNanos = System.nanoTime() - startTime;}</pre>
*
* <p>To compare elapsed time against a timeout, use <pre> {@code
* if (System.nanoTime() - startTime >= timeoutNanos) ...}</pre>
* instead of <pre> {@code
* if (System.nanoTime() >= startTime + timeoutNanos) ...}</pre>
* because of the possibility of numerical overflow.
*
* @return the current value of the running Java Virtual Machine's
*         high-resolution time source, in nanoseconds
* @since 1.5
*/
@HotSpotIntrinsicCandidate
public static native long nanoTime();盲泛,

官方的注釋也比較長(zhǎng),我們挑重點(diǎn)的內(nèi)容翻譯一下:
這個(gè)方法會(huì)返回一個(gè)當(dāng)前運(yùn)行java虛擬機(jī)的高精度的時(shí)間戳键耕,精度到了納秒寺滚。可能對(duì)納秒還沒什么概念屈雄,我們來比較一下
1s = 1000ms
1s = 1000000000ns
毫秒其實(shí)已經(jīng)是一個(gè)挺小的單位了村视,看到1秒等于那么多納秒,可以看到所謂的高精度也是名副其實(shí)酒奶。
但是我們知道currentTimeMillis()返回的是1970年1月1日0點(diǎn)到現(xiàn)在的毫秒數(shù)蚁孔,那么nanoTime()的返回值的含義呢?繼續(xù)看注釋惋嚎,其實(shí)這個(gè)時(shí)間就是java虛擬機(jī)給定的一個(gè)時(shí)間作為起始杠氢,并不具備實(shí)際的含義,所以它的作用并不像毫秒時(shí)間戳一樣瘸彤,單獨(dú)就可以表示一個(gè)時(shí)刻修然,它常用的地方是做2個(gè)時(shí)間點(diǎn)的比較,看中間經(jīng)過了多少時(shí)間质况。例如愕宋,我們要知道一個(gè)方法運(yùn)行耗費(fèi)了多少時(shí)間,那么我們就可以在方法的起止獲取2個(gè)nanoTime()结榄,相減得到的值就是這個(gè)方法以納秒為單位的運(yùn)行時(shí)間中贝。

4. 【強(qiáng)制】不允許在程序任何地方中使用:1)java.sql.Date 2)java.sql.Time 3)java.sql.Timestamp。

5. 【強(qiáng)制】不要在程序中寫死一年為 365 天臼朗,避免在公歷閏年時(shí)出現(xiàn)日期轉(zhuǎn)換錯(cuò)誤或程序邏輯錯(cuò)誤邻寿。

6. 【推薦】避免公歷閏年 2 月問題蝎土。閏年的 2 月份有 29 天,一年后的那一天不可能是 2 月 29日绣否。

這3條就不多做解讀了誊涯,就是字面含義,大家在代碼中如果用到了時(shí)間相關(guān)的函數(shù)的話蒜撮,上面3條應(yīng)該本來就有注意吧暴构。

7. 【推薦】使用枚舉值來指代月份。如果使用數(shù)字段磨,注意 Date取逾,Calendar 等日期相關(guān)類的月份month 取值在 0-11 之間。

這條在第1條的解讀里面也有提到苹支,就是這個(gè)月份在系統(tǒng)中是0-11砾隅,跟生活中的1-12這個(gè)差異而已。

上面就是我對(duì)《Java開發(fā)手冊(cè)(泰山版)》的中新增的日期時(shí)間類規(guī)范的一些理解债蜜,如果有不對(duì)的地方晴埂,歡迎拍磚,大家一起學(xué)習(xí)討論策幼。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末邑时,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子特姐,更是在濱河造成了極大的恐慌,老刑警劉巖黍氮,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唐含,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡沫浆,警方通過查閱死者的電腦和手機(jī)捷枯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來专执,“玉大人淮捆,你說我怎么就攤上這事”竟桑” “怎么了攀痊?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)拄显。 經(jīng)常有香客問我苟径,道長(zhǎng),這世上最難降的妖魔是什么躬审? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任棘街,我火速辦了婚禮蟆盐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遭殉。我一直安慰自己石挂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布险污。 她就那樣靜靜地躺著痹愚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪罗心。 梳的紋絲不亂的頭發(fā)上里伯,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音渤闷,去河邊找鬼疾瓮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛飒箭,可吹牛的內(nèi)容都是我干的狼电。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼弦蹂,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼肩碟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起凸椿,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤削祈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后脑漫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體髓抑,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年优幸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吨拍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡网杆,死狀恐怖羹饰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情碳却,我是刑警寧澤队秩,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站追城,受9級(jí)特大地震影響刹碾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜座柱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一迷帜、第九天 我趴在偏房一處隱蔽的房頂上張望物舒。 院中可真熱鬧,春花似錦戏锹、人聲如沸冠胯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荠察。三九已至,卻和暖如春奈搜,著一層夾襖步出監(jiān)牢的瞬間悉盆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工馋吗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留焕盟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓宏粤,卻偏偏與公主長(zhǎng)得像脚翘,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子绍哎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345