阿里巴巴java开发手册学习
本文對(duì)阿里巴巴java開發(fā)手冊(cè)中需要注意的點(diǎn)予以記錄
1.編程規(guī)約
-
類名中包含領(lǐng)域模型如DO/BO/DTO/VO時(shí)要 全部大寫,如UserDTO.
-
抽象類要以Abstract或Base開頭,異常類以Exception結(jié)尾,測(cè)試類要以所測(cè)試的類開頭,Test結(jié)尾。
-
杜絕不規(guī)范的縮寫
-
將設(shè)計(jì)模式體現(xiàn)在類名中,有利于閱讀者快速理解架構(gòu),如OrderFactory LoginProxy ResourceObsever
-
service和dao層的方法命名推薦:get list count save/insert remove/delete update作為前綴
-
不要使用一個(gè)常量類來維護(hù)所有常量,應(yīng)該分類分開維護(hù)
-
任何運(yùn)算符前后都應(yīng)該有一個(gè)空格
-
所有覆寫方法,必須添加@Override注解
-
POJO類必須寫toString方法,便于排查問題(拋出異常時(shí)會(huì)調(diào)用該類的toString打印屬性值)
-
final可以提高程序效率,多考慮屬性,方法,類是否可以定義成final
-
關(guān)于 hashCode 和 equals 的處理,遵循如下規(guī)則:
1) 只要重寫 equals ,就必須重寫 hashCode 。
2) 因?yàn)?Set 存儲(chǔ)的是不重復(fù)的對(duì)象,依據(jù) hashCode 和 equals 進(jìn)行判斷,所以 Set 存儲(chǔ)的
對(duì)象必須重寫這兩個(gè)方法。
3) 如果自定義對(duì)象做為 Map 的鍵,那么必須重寫 hashCode 和 equals 。
正例: String 重寫了 hashCode 和 equals 方法,所以我們可以非常愉快地使用 String 對(duì)象
作為 key 來使用。 -
ArrayList 的 subList 結(jié)果不可強(qiáng)轉(zhuǎn)成 ArrayList ,否則會(huì)拋出 ClassCastException異常: java . util . RandomAccessSubList cannot be cast to java . util . ArrayList ;說明: subList 返回的是 ArrayList 的內(nèi)部類 SubList ,并不是 ArrayList ,而是ArrayList 的一個(gè)視圖,對(duì)于 SubList 子列表的所有操作最終會(huì)反映到原列表上。 subList 場(chǎng)景中,高度注意對(duì)原集合元素個(gè)數(shù)的修改,會(huì)導(dǎo)致子列表的遍歷、增加、刪除均產(chǎn)生 ConcurrentModificationException 異常。但實(shí)際開發(fā)中subList基本不會(huì)使用。
-
使用集合轉(zhuǎn)數(shù)組的方法,必須使用集合的 toArray(T[] array) ,傳入的是類型完全一樣的數(shù)組,大小就是 list . size() 。
反例:直接使用 toArray 無參方法存在問題,此方法返回值只能是 Object[] 類,若強(qiáng)轉(zhuǎn)其它類型數(shù)組將出現(xiàn) ClassCastException 錯(cuò)誤。
正例:
說明:使用 toArray 帶參方法,入?yún)⒎峙涞臄?shù)組空間不夠大時(shí), toArray 方法內(nèi)部將重新分配內(nèi)存空間,并返回新數(shù)組地址 ; 如果數(shù)組元素大于實(shí)際所需,下標(biāo)為 [ list . size() ] 的數(shù)組元素將被置為 null ,其它數(shù)組元素保持原值,因此最好將方法入?yún)?shù)組大小定義與集合元素個(gè)數(shù)一致。
- 使用工具類 Arrays . asList() 把數(shù)組轉(zhuǎn)換成集合時(shí),不能使用其修改集合相關(guān)的方法,它的 add / remove / clear 方法會(huì)拋出 UnsupportedOperationException 異常。
說明: asList 的返回對(duì)象是一個(gè) Arrays 內(nèi)部類,并沒有實(shí)現(xiàn)集合的修改方法。 Arrays . asList體現(xiàn)的是適配器模式,只是轉(zhuǎn)換接口,后臺(tái)的數(shù)據(jù)仍是數(shù)組。
String[] str = new String[] { “a”, “b” };
List list = Arrays.asList(str);
第一種情況: list.add(“c”); 運(yùn)行時(shí)異常。
第二種情況: str[0]= “gujin”; 那么 list.get(0) 也會(huì)隨之修改。
如圖:返回的是Arrays的內(nèi)部類
- 【強(qiáng)制】不要在 foreach 循環(huán)里進(jìn)行元素的 remove / add 操作。 remove 元素請(qǐng)使用 Iterator方式,如果并發(fā)操作,需要對(duì) Iterator 對(duì)象加鎖。
反例:
說明:以上代碼的執(zhí)行結(jié)果肯定會(huì)出乎大家的意料,那么試一下把“1”換成“2”,會(huì)是同樣的結(jié)果嗎?會(huì)報(bào)ConcurrentModificationException并發(fā)修改異常。
正例:
- 集合初始化時(shí),盡量指定集合初始值大小。
說明: ArrayList 盡量使用 ArrayList(int initialCapacity) 初始化 - 使用 entrySet 遍歷 Map 類集合 KV ,而不是 keySet 方式進(jìn)行遍歷。
說明: keySet 其實(shí)是遍歷了 2 次,一次是轉(zhuǎn)為 Iterator 對(duì)象,另一次是從 hashMap 中取出key 所對(duì)應(yīng)的 value 。而 entrySet 只是遍歷了一次就把 key 和 value 都放到了 entry 中,效率更高。 values() 返回的是 V 值集合,是一個(gè) list 集合對(duì)象 ;keySet() 返回的是 K 值集合,是一個(gè) Set 集合對(duì)象 ;entrySet() 返回的是 K - V 值組合集合。
map的四種遍歷方式
如果是 JDK 8,使用 Map . foreach 方法。
- 由于 HashMap 的干擾,很多人認(rèn)為 ConcurrentHashMap 是可以置入 null 值,注意存儲(chǔ)null 值時(shí)會(huì)拋出 NPE 異常
| Hashtable | 不允許為 null | 不允許為 null | Dictionary | 線程安全 |
| ConcurrentHashMap | 不允許為 null | 不允許為 null | AbstractMap | 分段鎖技術(shù) |
| TreeMap | 不允許為 null | 允許為 null | AbstractMap | 線程不安全 |
| HashMap | 不允許為 null | 不允許為 null | AbstractMap | 線程不安全 |
2.并發(fā)處理
- 線程資源必須通過線程池提供,不允許在應(yīng)用中自行顯式創(chuàng)建線程。使用線程池的好處是減少在創(chuàng)建和銷毀線程上所花的時(shí)間以及系統(tǒng)資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統(tǒng)創(chuàng)建大量同類線程而導(dǎo)致消耗完內(nèi)存或者“過度切換”的問題。線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)。
說明: Executors 返回的線程池對(duì)象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool :
允許的請(qǐng)求隊(duì)列長(zhǎng)度為 Integer.MAX_VALUE ,可能會(huì)堆積大量的請(qǐng)求,從而導(dǎo)致 OOM 。
2) CachedThreadPool 和 ScheduledThreadPool :
允許的創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE ,可能會(huì)創(chuàng)建大量的線程,從而導(dǎo)致 OOM
而且注意自定義的線程池一定要定義有意義的線程池名稱,便于查看日志排查問題。
- SimpleDateFormat 是線程不安全的類,一般不要定義為 static 變量,如果定義為static ,必須加鎖,或者使用 DateUtils 工具類。
正例:注意線程安全,使用 DateUtils 。亦推薦如下處理:
說明:如果是 JDK 8 ,可以使用 Instant 代替 Date , LocalDateTime 代替 Calendar ,DateTimeFormatter 代替 Simpledateformatter ,官方給出的解釋: simple beautiful strong immutable thread - safe 。
3.邏輯處理
- 在一個(gè) switch 塊內(nèi),每個(gè) case 要么通過 break / return 等來終止,要么注釋說明程序?qū)⒗^續(xù)執(zhí)行到哪一個(gè) case 為止 ; 在一個(gè) switch 塊內(nèi),都必須包含一個(gè) default 語句并且放在最后,即使它什么代碼也沒有
- 推薦盡量少用 else , if - else 的方式可以改寫成:
if(condition){
…
return obj;
}
// 接著寫 else 的業(yè)務(wù)邏輯代碼;
說明:如果非得使用 if()…else if()…else… 方式表達(dá)邏輯,【強(qiáng)制】請(qǐng)勿超過 3 層,超過請(qǐng)使用狀態(tài)設(shè)計(jì)模式。
正例:邏輯上超過 3 層的 if-else 代碼可以使用switch語句,或者狀態(tài)模式來實(shí)現(xiàn) - 方法中需要進(jìn)行參數(shù)校驗(yàn)的場(chǎng)景:
1 ) 調(diào)用頻次低的方法。
2 ) 執(zhí)行時(shí)間開銷很大的方法,參數(shù)校驗(yàn)時(shí)間幾乎可以忽略不計(jì),但如果因?yàn)閰?shù)錯(cuò)誤導(dǎo)致
中間執(zhí)行回退,或者錯(cuò)誤,那得不償失。
3 ) 需要極高穩(wěn)定性和可用性的方法。
4 ) 對(duì)外提供的開放接口,不管是 RPC / API / HTTP 接口。
5) 敏感權(quán)限入口 - 方法中不需要參數(shù)校驗(yàn)的場(chǎng)景:
1 ) 極有可能被循環(huán)調(diào)用的方法,不建議對(duì)參數(shù)進(jìn)行校驗(yàn)。但在方法說明里必須注明外部參數(shù)檢查。
2 ) 底層的方法調(diào)用頻度都比較高,一般不校驗(yàn)。畢竟是像純凈水過濾的最后一道,參數(shù)錯(cuò)誤不太可能到底層才會(huì)暴露問題。一般 DAO 層與 Service 層都在同一個(gè)應(yīng)用中,部署在同一臺(tái)服務(wù)器中,所以 DAO 的參數(shù)校驗(yàn),可以省略。
3 ) 被聲明成 private 只會(huì)被自己代碼所調(diào)用的方法,如果能夠確定調(diào)用方法的代碼傳入?yún)?shù)已經(jīng)做過檢查或者肯定不會(huì)有問題,此時(shí)可以不校驗(yàn)參數(shù) - 所有的枚舉類型字段必須要有注釋,說明每個(gè)數(shù)據(jù)項(xiàng)的用途.但一般枚舉都有description屬性來描述意義。
- 獲取當(dāng)前毫秒數(shù) System . currentTimeMillis(); 而不是 new Date() . getTime();
說明:如果想獲取更加精確的納秒級(jí)時(shí)間值,用 System . nanoTime() 。在 JDK 8 中,針對(duì)統(tǒng)計(jì)時(shí)間等場(chǎng)景,推薦使用 Instant 類。
4.異常處理和日志記錄
- 有 try 塊放到了事務(wù)代碼中, catch 異常后,如果需要回滾事務(wù),一定要注意手動(dòng)回滾事務(wù)。
- 不能在 finally 塊中使用 return , finally 塊中的 return 返回后方法結(jié)束執(zhí)行,不會(huì)再執(zhí)行 try 塊中的 return 語句
- 方法的返回值可以為 null ,不強(qiáng)制返回空集合,或者空對(duì)象等,必須添加注釋充分說明什么情況下會(huì)返回 null 值。調(diào)用方需要進(jìn)行 null 判斷防止 NPE 問題。
說明:本規(guī)約明確防止 NPE 是調(diào)用者的責(zé)任。即使被調(diào)用方法返回空集合或者空對(duì)象,對(duì)調(diào)用者來說,也并非高枕無憂,必須考慮到遠(yuǎn)程調(diào)用失敗,運(yùn)行時(shí)異常等場(chǎng)景返回 null 的情況 - 【推薦】防止 NPE ,是程序員的基本修養(yǎng),注意 NPE 產(chǎn)生的場(chǎng)景:
1 ) 返回類型為包裝數(shù)據(jù)類型,有可能是 null ,返回 int 值時(shí)注意判空。
反例: public int f() { return Integer 對(duì)象}; 如果為 null ,自動(dòng)解箱拋 NPE 。
2 ) 數(shù)據(jù)庫(kù)的查詢結(jié)果可能為 null 。注:其實(shí)現(xiàn)在很多框架在返回查詢結(jié)果時(shí)已經(jīng)進(jìn)行處理不會(huì)返回null
3 ) 集合里的元素即使 isNotEmpty ,取出的數(shù)據(jù)元素也可能為 null 。
4 ) 遠(yuǎn)程調(diào)用返回對(duì)象,一律要求進(jìn)行 NPE 判斷。
5 ) 對(duì)于 Session 中獲取的數(shù)據(jù),建議 NPE 檢查,避免空指針。
6 ) 級(jí)聯(lián)調(diào)用 obj . getA() . getB() . getC(); 一連串調(diào)用,易產(chǎn)生 NPE - 在代碼中使用“拋異常”還是“返回錯(cuò)誤碼”,對(duì)于公司外的 http / api 開放接口必須使用錯(cuò)誤碼 ; 而應(yīng)用內(nèi)部推薦異常拋出 ; 跨應(yīng)用間 RPC 調(diào)用優(yōu)先考慮使用 Result 方式,封裝success 、code、msg。
說明:關(guān)于 RPC 方法返回方式使用 Result 方式的理由:
1 ) 使用拋異常返回方式,調(diào)用方如果沒有捕獲到就會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤。
2 ) 如果不加棧信息,只是 new 自定義異常,加入自己的理解的 error message ,對(duì)于調(diào)用端解決問題的幫助不會(huì)太多。如果加了棧信息,在頻繁調(diào)用出錯(cuò)的情況下,數(shù)據(jù)序列化和傳輸?shù)男阅軗p耗也是問題 - 應(yīng)用中不可直接使用日志系統(tǒng) (Log 4 j 、 Logback) 中的 API ,而應(yīng)依賴使用日志框架SLF 4 J 中的 API ,使用門面模式的日志框架,有利于維護(hù)和各個(gè)類的日志處理方式統(tǒng)一。
注:這里變量名logger也并沒有大寫。
- 日志文件推薦至少保存 15 天,因?yàn)橛行┊惓>邆湟浴爸堋睘轭l次發(fā)生的特點(diǎn)
- 應(yīng)用中的擴(kuò)展日志 ( 如打點(diǎn)、臨時(shí)監(jiān)控、訪問日志等 ) 命名方式:appName _ logType _ logName . log 。 logType :日志類型,推薦分類有stats / desc / monitor / visit 等 ;logName :日志描述。這種命名的好處:通過文件名就可知道日志文件屬于什么應(yīng)用,什么類型,什么目的,也有利于歸類查找。
正例: mppserver 應(yīng)用中單獨(dú)監(jiān)控時(shí)區(qū)轉(zhuǎn)換異常,如:mppserver _ monitor _ timeZoneConvert . log
說明:推薦對(duì)日志進(jìn)行分類,錯(cuò)誤日志和業(yè)務(wù)日志盡量分開存放,便于開發(fā)人員查看,也便于通過日志對(duì)系統(tǒng)進(jìn)行及時(shí)監(jiān)控。 - 對(duì) trace / debug / info 級(jí)別的日志輸出,必須使用條件輸出形式或者使用占位符的方式。
說明: logger . debug( " Processing trade with id : " + id + " symbol : " + symbol);**如果日志級(jí)別是 warn ,上述日志不會(huì)打印,但是會(huì)執(zhí)行字符串拼接操作,如果 symbol 是對(duì)象,會(huì)執(zhí)行 toString() 方法,浪費(fèi)了系統(tǒng)資源,執(zhí)行了上述操作,最終日志卻沒有打印。**原來如此
正例: ( 條件 )
正例: ( 占位符 )
logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);- 異常信息應(yīng)該包括兩類信息:案發(fā)現(xiàn)場(chǎng)信息和異常堆棧信息。如果不處理,那么往上拋。
正例: logger.error(各類參數(shù)或者對(duì)象 toString + "_" + e.getMessage(), e); e會(huì)顯示堆棧信息。 - 謹(jǐn)慎地記錄日志。生產(chǎn)環(huán)境禁止輸出 debug 日志 ; 有選擇地輸出 info 日志 ; 如果使用 warn 來記錄剛上線時(shí)的業(yè)務(wù)行為信息,一定要注意日志輸出量的問題,避免把服務(wù)器磁盤撐爆,并記得及時(shí)刪除這些觀察日志。
說明:大量地輸出無效日志,不利于系統(tǒng)性能提升,也不利于快速定位錯(cuò)誤點(diǎn)。記錄日志時(shí)請(qǐng)思考:這些日志真的有人看嗎?看到這條日志你能做什么?能不能給問題排查帶來好處?
5.數(shù)據(jù)庫(kù)規(guī)約
- 單表行數(shù)超過 500 萬行或者單表容量超過 2 GB ,才推薦進(jìn)行分庫(kù)分表。
說明:如果預(yù)計(jì)三年后的數(shù)據(jù)量根本達(dá)不到這個(gè)級(jí)別,請(qǐng)不要在創(chuàng)建表時(shí)就分庫(kù)分表 - 業(yè)務(wù)上具有唯一特性的字段,即使是組合字段,也必須建成唯一索引。
說明:不要以為唯一索引影響了 insert 速度,這個(gè)速度損耗可以忽略,但提高查找速度是明顯的 ; 另外,即使在應(yīng)用層做了非常完善的校驗(yàn)和控制,只要沒有唯一索引,根據(jù)墨菲定律,必然有臟數(shù)據(jù)產(chǎn)生。注:這個(gè)值得考慮 - 超過三個(gè)表禁止 join 。需要 join 的字段,數(shù)據(jù)類型保持絕對(duì)一致 ; 多表關(guān)聯(lián)查詢時(shí),保證被關(guān)聯(lián)的字段需要有索引。
說明:即使雙表 join 也要注意表索引、 SQL 性能。注:實(shí)際上,現(xiàn)在很多都全部采取單表,禁止關(guān)聯(lián),簡(jiǎn)化數(shù)據(jù)庫(kù)維護(hù)。 - 頁(yè)面搜索嚴(yán)禁左模糊或者全模糊,如果需要請(qǐng)走搜索引擎來解決。
說明:索引文件具有 B - Tree 的最左前綴匹配特性,如果左邊的值未確定,那么無法使用此索引。這個(gè)很難做到,如設(shè)備名稱。 - 創(chuàng)建索引時(shí)避免有如下極端誤解:
1 ) 誤認(rèn)為一個(gè)查詢就需要建一個(gè)索引。
2 ) 誤認(rèn)為索引會(huì)消耗空間、嚴(yán)重拖慢更新和新增速度。
3 ) 誤認(rèn)為唯一索引一律需要在應(yīng)用層通過“先查后插”方式解決 - 在代碼中寫分頁(yè)查詢邏輯時(shí),若 count 為 0 應(yīng)直接返回,避免執(zhí)行后面的分頁(yè)語句
- 不得使用外鍵與級(jí)聯(lián),一切外鍵概念必須在應(yīng)用層解決。
說明: ( 概念解釋 ) 學(xué)生表中的 student _ id 是主鍵,那么成績(jī)表中的 student _ id 則為外鍵。
如果更新學(xué)生表中的 student _ id ,同時(shí)觸發(fā)成績(jī)表中的 student _ id 更新,則為級(jí)聯(lián)更新。外鍵與級(jí)聯(lián)更新適用于單機(jī)低并發(fā),不適合分布式、高并發(fā)集群 ; 級(jí)聯(lián)更新是強(qiáng)阻塞,存在數(shù)據(jù)庫(kù)更新風(fēng)暴的風(fēng)險(xiǎn) ; 外鍵影響數(shù)據(jù)庫(kù)的插入速度。現(xiàn)在基本都設(shè)計(jì)成單表,在應(yīng)用中處理關(guān)聯(lián)邏輯。 - 禁止使用存儲(chǔ)過程,存儲(chǔ)過程難以調(diào)試和擴(kuò)展,更沒有移植性
- 數(shù)據(jù)訂正時(shí),刪除和修改記錄時(shí),要先 select ,避免出現(xiàn)誤刪除,確認(rèn)無誤才能執(zhí)行更新語句。沒毛病更新一般是先根據(jù)id查詢出值,再去修改。
- 不要用 resultClass 當(dāng)返回參數(shù),即使所有類屬性名與數(shù)據(jù)庫(kù)字段一一對(duì)應(yīng),也需要定義 ; 反過來,每一個(gè)表也必然有一個(gè)與之對(duì)應(yīng)。
說明:配置映射關(guān)系,使字段與 DO 類解耦,方便維護(hù)。不能將entity直接作為返回值! - 更新數(shù)據(jù)表記錄時(shí),必須同時(shí)更新記錄對(duì)應(yīng)的 gmt _ modified 字段值為當(dāng)前時(shí)間。規(guī)范意思是所有表都應(yīng)有創(chuàng)建時(shí)間和修改時(shí)間兩個(gè)字段。
- @ Transactional 事務(wù)不要濫用。事務(wù)會(huì)影響數(shù)據(jù)庫(kù)的 QPS ,另外使用事務(wù)的地方需要考慮各方面的回滾方案,包括緩存回滾、搜索引擎回滾、消息補(bǔ)償、統(tǒng)計(jì)修正等
其他
- 所有 pom 文件中的依賴聲明放在< dependencies >語句塊中,所有版本仲裁放在< dependencyManagement >語句塊中。
說明:< dependencyManagement >里只是聲明版本,并不實(shí)現(xiàn)引入,因此子項(xiàng)目需要顯式的聲明依賴, version 和 scope 都讀取自父 pom 。而< dependencies >所有聲明在主 pom 的< dependencies >里的依賴都會(huì)自動(dòng)引入,并默認(rèn)被所有的子項(xiàng)目繼承。
總結(jié)
以上是生活随笔為你收集整理的阿里巴巴java开发手册学习的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: windows常用技巧
- 下一篇: spring的aware学习