近期眶痰,阿里巴巴發(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年歷花嘶,看到下圖:
可以看到,這一周是從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í)討論策幼。