1. 所有整型包裝類對象之間值的比較,全部使用 equals 方法比較。
說明:對于 Integer var = ? 在-128 至 127 范圍內(nèi)的賦值,Integer 對象是在 IntegerCache.cache 產(chǎn)生,會復(fù)用已有對象,這個(gè)區(qū)間內(nèi)的 Integer 值可以直接使用==進(jìn)行判斷,但是這個(gè)區(qū)間之外的所有數(shù)據(jù),都會在堆上產(chǎn)生,并不會復(fù)用已有對象,這是一個(gè)大坑,推薦使用 equals 方法進(jìn)行判斷。
2. 浮點(diǎn)數(shù)之間的等值判斷,基本數(shù)據(jù)類型不能用==來比較,包裝數(shù)據(jù)類型不能用equals 來判斷。
3. 定義數(shù)據(jù)對象 DO 類時(shí),屬性類型要與數(shù)據(jù)庫字段類型相匹配。
正例:數(shù)據(jù)庫字段的 bigint 必須與類屬性的 Long 類型相對應(yīng)。
反例:某個(gè)案例的數(shù)據(jù)庫表 id 字段定義類型 bigint unsigned,實(shí)際類對象屬性為 Integer,隨著 id 越來越大,超過 Integer 的表示范圍而溢出成為負(fù)數(shù)。
4. 關(guān)于基本數(shù)據(jù)類型與包裝數(shù)據(jù)類型的使用標(biāo)準(zhǔn)如下:
1) 所有的 POJO 類屬性必須使用包裝數(shù)據(jù)類型。
2) RPC 方法(Remote Procedure Call——遠(yuǎn)程過程調(diào)用)的返回值和參數(shù)必須使用包裝數(shù)據(jù)類型。
正例:數(shù)據(jù)庫的查詢結(jié)果可能是 null,因?yàn)樽詣硬鹣洌没緮?shù)據(jù)類型接收有 NPE 風(fēng)險(xiǎn)。
5. 使用 Map 的方法 keySet() / values() / entrySet() 返回集合對象時(shí),不可以對其進(jìn)行添加元素操作,否則會拋出 UnsupportedOperationException 異常。
6. 使用集合轉(zhuǎn)數(shù)組的方法,必須使用集合的 toArray(T[] array),傳入的是類型完全一致、長度為 0 的空數(shù)組。
反例:直接使用 toArray 無參方法存在問題,此方法返回值只能是 Object[]類,若強(qiáng)轉(zhuǎn)其它類型數(shù)組將出現(xiàn) ClassCastException 錯(cuò)誤。
正例:
List<String> list = new ArrayList<>(2);
list.add("guan");
list.add("bao");
String[] array = list.toArray(new String[0]);
說明:使用 toArray 帶參方法,數(shù)組空間大小的 length:
1) 等于 0,動態(tài)創(chuàng)建與 size 相同的數(shù)組,性能最好。
2) 大于 0 但小于 size,重新創(chuàng)建大小等于 size 的數(shù)組,增加 GC 負(fù)擔(dān)。
3) 等于 size,在高并發(fā)情況下,數(shù)組創(chuàng)建完成之后,size 正在變大的情況下,負(fù)面影響與上相同。
4) 大于 size,空間浪費(fèi),且在 size 處插入 null 值,存在 NPE 隱患。
7. 在使用 Collection 接口任何實(shí)現(xiàn)類的 addAll()方法時(shí),都要對輸入的集合參數(shù)進(jìn)行NPE 判斷。
說明:在 ArrayList#addAll 方法的第一行代碼即 Object[] a = c.toArray(); 其中 c 為輸入集合參數(shù),如果為 null,則直接拋出異常。
8. 使用工具類 Arrays.asList()把數(shù)組轉(zhuǎn)換成集合時(shí),不能使用其修改集合相關(guān)的方法,它的 add/remove/clear 方法會拋出UnsupportedOperationException 異常。
說明:asList 的返回對象是一個(gè) Arrays 內(nèi)部類,并沒有實(shí)現(xiàn)集合的修改方法。Arrays.asList 體現(xiàn)的是適配器模式,只是轉(zhuǎn)換接口,后臺的數(shù)據(jù)仍是數(shù)組。
String[] str = new String[] { "yang", "hao" };
List list = Arrays.asList(str);
第一種情況:list.add("yangguanbao"); 運(yùn)行時(shí)異常。
第二種情況:str[0] = "changed"; 也會隨之修改,反之亦然。
9. 不要在 foreach 循環(huán)里進(jìn)行元素的 remove/add 操作。remove 元素請使用
Iterator 方式,如果并發(fā)操作,需要對 Iterator 對象加鎖。
正例:
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (刪除元素的條件) {
iterator.remove();
}
}
反例:
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
10. 高度注意 Map 類集合 K/V 能不能存儲 null 值的情況,如下表格:
集合類 | Key | Value | Super | 說明 |
---|---|---|---|---|
Hashtable | 不允許為 null | 不允許為 null | Dictionary | 線程安全 |
ConcurrentHashMap | 不允許為 null | 不允許為 null | AbstractMap | 鎖分段技術(shù)(JDK8:CAS) |
TreeMap | 不允許為 null | 允許為 null | AbstractMap | 線程不安全 |
HashMap | 允許為 null | 允許為 null | AbstractMap | 線程不安全 |
反例:由于 HashMap 的干擾,很多人認(rèn)為 ConcurrentHashMap 是可以置入 null 值,而事實(shí)上,存儲null 值時(shí)會拋出 NPE 異常。
11. 在一個(gè) switch 塊內(nèi),每個(gè) case 要么通過 continue/break/return 等來終止,要么注釋說明程序?qū)⒗^續(xù)執(zhí)行到哪一個(gè) case 為止;在一個(gè) switch 塊內(nèi),都必須包含一個(gè)default 語句并且放在最后,即使它什么代碼也沒有。
說明:注意 break 是退出 switch 語句塊,而 return 是退出方法體。
12. 在高并發(fā)場景中,避免使用”等于”判斷作為中斷或退出的條件。
說明:如果并發(fā)控制沒有處理好,容易產(chǎn)生等值判斷被“擊穿”的情況,使用大于或小于的區(qū)間判斷條件來代替。
反例:判斷剩余獎品數(shù)量等于 0 時(shí),終止發(fā)放獎品,但因?yàn)椴l(fā)處理錯(cuò)誤導(dǎo)致獎品數(shù)量瞬間變成了負(fù)數(shù),這樣的話,活動無法終止。
13. 類、類屬性、類方法的注釋必須使用 Javadoc 規(guī)范,使用/*內(nèi)容/格式,不得使用// xxx 方式。
說明:在 IDE 編輯窗口中,Javadoc 方式會提示相關(guān)注釋,生成 Javadoc 可以正確輸出相應(yīng)注釋;在 IDE中,工程調(diào)用方法時(shí),不進(jìn)入方法即可懸浮提示方法、參數(shù)、返回值的意義,提高閱讀效率。
14. 所有的抽象方法 ( 包括接口中的方法 ) 必須要用 Javadoc 注釋、除了返回值、參數(shù)、異常說明外,還必須指出該方法做什么事情,實(shí)現(xiàn)什么功能。
說明:對子類的實(shí)現(xiàn)要求,或者調(diào)用注意事項(xiàng),請一并說明。
15. 所有的類都必須添加創(chuàng)建者和創(chuàng)建日期。
16. 方法內(nèi)部單行注釋,在被注釋語句上方另起一行,使用//注釋。方法內(nèi)部多行注釋使用/* */注釋,注意與代碼對齊。
17. 后臺輸送給頁面的變量必須加 $!{var} ——中間的感嘆號。
說明:如果 var 等于 null 或者不存在,那么${var}會直接顯示在頁面上。
18. 注意 Math . random() 這個(gè)方法返回是 double 類型,注意取值的范圍 0≤ x <1 ( 能夠取到零值,注意除零異常 ) ,如果想獲取整數(shù)類型的隨機(jī)數(shù),不要將 x 放大 10 的若干倍然后取整,直接使用 Random 對象的 nextInt 或者 nextLong 方法。
19. 獲取當(dāng)前毫秒數(shù) System . currentTimeMillis(); 而不是 new Date() . getTime();
說明:如果想獲取更加精確的納秒級時(shí)間值,使用 System.nanoTime()的方式。在 JDK8 中,針對統(tǒng)計(jì)時(shí)間等場景,推薦使用 Instant 類。
20. 日期格式化時(shí),傳入 pattern 中表示年份統(tǒng)一使用小寫的 y。
說明:日期格式化時(shí), yyyy 表示當(dāng)天所在的年,而大寫的 YYYY 代表是 week in which year(JDK7 之后引入的概念),意思是當(dāng)天所在的周屬于的年份,一周從周日開始,周六結(jié)束,只要本周跨年,返回的 YYYY 就是下一年。另外需要注意:
? 表示月份是大寫的 M
? 表示分鐘則是小寫的 m
? 24 小時(shí)制的是大寫的 H
? 12 小時(shí)制的則是小寫的 h
正例:表示日期和時(shí)間的格式如下所示:
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
21. 防止 NPE ,是程序員的基本修養(yǎng),注意 NPE 產(chǎn)生的場景:
1) 返回類型為基本數(shù)據(jù)類型,return 包裝數(shù)據(jù)類型的對象時(shí),自動拆箱有可能產(chǎn)生 NPE。
反例:public int f() { return Integer 對象}, 如果為 null,自動解箱拋 NPE。
2) 數(shù)據(jù)庫的查詢結(jié)果可能為 null。
3) 集合里的元素即使 isNotEmpty,取出的數(shù)據(jù)元素也可能為 null。
4) 遠(yuǎn)程調(diào)用返回對象時(shí),一律要求進(jìn)行空指針判斷,防止 NPE。
5) 對于 Session 中獲取的數(shù)據(jù),建議進(jìn)行 NPE 檢查,避免空指針。
6) 級聯(lián)調(diào)用 obj.getA().getB().getC();一連串調(diào)用,易產(chǎn)生 NPE。
正例:使用 JDK8 的 Optional 類來防止 NPE 問題。
22. 應(yīng)用中不可直接使用日志系統(tǒng) (Log 4 j 、 Logback) 中的 API ,而應(yīng)依賴使用日志框架SLF 4 J 中的 API ,使用門面模式的日志框架,有利于維護(hù)和各個(gè)類的日志處理方式統(tǒng)一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Test.class);
23. 在日志輸出時(shí),字符串變量之間的拼接使用占位符的方式。
說明:因?yàn)?String 字符串的拼接會使用 StringBuilder 的 append()方式,有一定的性能損耗。使用占位符僅是替換動作,可以有效提升性能。
正例: logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);
24. MySQL表達(dá)是與否概念的字段,必須使用 is_xxx 的方式命名,數(shù)據(jù)類型是 unsignedtinyint(1 表示是,0 表示否)。
說明:任何字段如果為非負(fù)數(shù),必須是 unsigned。
注意:POJO 類中的任何布爾類型的變量,都不要加 is 前綴,所以,需要在<resultMap>設(shè)置從 is_xxx到 Xxx 的映射關(guān)系。數(shù)據(jù)庫表示是與否的值,使用 tinyint 類型,堅(jiān)持 is_xxx 的命名方式是為了明確其取值含義與取值范圍。
正例:表達(dá)邏輯刪除的字段名 is_deleted,1 表示刪除,0 表示未刪除。
25. MySQL表名、字段名必須使用小寫字母或數(shù)字 , 禁止出現(xiàn)數(shù)字開頭,禁止兩個(gè)下劃線中間只出現(xiàn)數(shù)字。數(shù)據(jù)庫字段名的修改代價(jià)很大,因?yàn)闊o法進(jìn)行預(yù)發(fā)布,所以字段名稱需要慎重考慮。
說明:MySQL 在 Windows 下不區(qū)分大小寫,但在 Linux 下默認(rèn)是區(qū)分大小寫。因此,數(shù)據(jù)庫名、表名、字段名,都不允許出現(xiàn)任何大寫字母,避免節(jié)外生枝。
正例:aliyun_admin,rdc_config,level3_name
反例:AliyunAdmin,rdcConfig,level_3_name
26. MySQL小數(shù)類型為 decimal ,禁止使用 float 和 double 。
說明:在存儲的時(shí)候,float 和 double 都存在精度損失的問題,很可能在比較值的時(shí)候,得到不正確的結(jié)果。如果存儲的數(shù)據(jù)范圍超過 decimal 的范圍,建議將數(shù)據(jù)拆成整數(shù)和小數(shù)并分開存儲。
27. MySQL如果存儲的字符串長度幾乎相等,使用 char 定長字符串類型。
28. MySQL varchar 是可變長字符串,不預(yù)先分配存儲空間,長度不要超過 5000,如果存儲長度大于此值,定義字段類型為 text ,獨(dú)立出來一張表,用主鍵來對應(yīng),避免影響其它字段索引效率。
29.MySQL表必備三字段:id, create_time, update_time。
說明:其中 id 必為主鍵,類型為 bigint unsigned、單表時(shí)自增、步長為 1。create_time, update_time的類型均為 datetime 類型。
30. MySQL合適的字符存儲長度,不但節(jié)約數(shù)據(jù)庫表空間、節(jié)約索引存儲,更重要的是提升檢索速度。
正例:如下表,其中無符號值可以避免誤存負(fù)數(shù),且擴(kuò)大了表示范圍。
對象 | 年齡區(qū)間 | 類型 | 字節(jié) | 表示范圍 |
---|---|---|---|---|
人 | 150 歲之內(nèi) | tinyint unsigned | 1 | 無符號值:0 到 255 |
龜 | 數(shù)百歲 | smallint unsigned | 2 | 無符號值:0 到 65535 |
恐龍化石 | 數(shù)千萬年 | int unsigned | 4 | 無符號值:0 到約 42.9 億 |
太陽 | 約 50 億年 | bigint unsigned | 8 | 無符號值:0 到約 10 的 19 次方 |
31. SQL 性能優(yōu)化的目標(biāo):至少要達(dá)到 range 級別,要求是 ref 級別,如果可以是consts 最好。
說明:
1) consts 單表中最多只有一個(gè)匹配行(主鍵或者唯一索引),在優(yōu)化階段即可讀取到數(shù)據(jù)。
2) ref 指的是使用普通的索引(normal index)。
3) range 對索引進(jìn)行范圍檢索。
反例:explain 表的結(jié)果,type=index,索引物理文件全掃描,速度非常慢,這個(gè) index 級別比較 range還低,與全表掃描是小巫見大巫。
32. SQL 語句:不要使用 count(列名)或 count(常量)來替代 count(),count()是 SQL92 定義的標(biāo)準(zhǔn)統(tǒng)計(jì)行數(shù)的語法,跟數(shù)據(jù)庫無關(guān),跟 NULL 和非 NULL 無關(guān)。
說明:count(*)會統(tǒng)計(jì)值為 NULL 的行,而 count(列名)不會統(tǒng)計(jì)此列為 NULL 值的行。
33. SQL 語句:count(distinct col) 計(jì)算該列除 NULL 之外的不重復(fù)行數(shù),注意 count(distinct col1, col2) 如果其中一列全為 NULL,那么即使另一列有不同的值,也返回為 0。
34. SQL 語句:當(dāng)某一列的值全是 NULL 時(shí),count(col)的返回結(jié)果為 0,但 sum(col)的返回結(jié)果為 NULL,因此使用 sum()時(shí)需注意 NPE 問題。
正例:使用如下方式來避免 sum 的 NPE 問題:SELECT IFNULL(SUM(column), 0) FROM table;
35. 分層領(lǐng)域模型規(guī)約:
? DO(Data Object):此對象與數(shù)據(jù)庫表結(jié)構(gòu)一一對應(yīng),通過 DAO 層向上傳輸數(shù)據(jù)源對象。
? DTO(Data Transfer Object):數(shù)據(jù)傳輸對象,Service 或 Manager 向外傳輸?shù)膶ο蟆?br>
? BO(Business Object):業(yè)務(wù)對象,由 Service 層輸出的封裝業(yè)務(wù)邏輯的對象。
? AO(Application Object):應(yīng)用對象,在 Web 層與 Service 層之間抽象的復(fù)用對象模型,極為貼近展示層,復(fù)用度不高。
? VO(View Object):顯示層對象,通常是 Web 向模板渲染引擎層傳輸?shù)膶ο蟆?br>
? Query:數(shù)據(jù)查詢對象,各層接收上層的查詢請求。注意超過 2 個(gè)參數(shù)的查詢封裝,禁止使用 Map 類來傳輸。