新聞中心
最近,阿里巴巴發(fā)布了《阿里巴巴Java開(kāi)發(fā)手冊(cè)》,總結(jié)了阿里人多年一線實(shí)戰(zhàn)中積累的研發(fā)流程規(guī)范,這些流程規(guī)范在一定程度上能夠保證最終的項(xiàng)目交付質(zhì)量,通過(guò)限制開(kāi)發(fā)人員的編程風(fēng)格、實(shí)現(xiàn)方式來(lái)避免研發(fā)人員在實(shí)踐中容易犯的錯(cuò)誤,同樣的問(wèn)題大家使用同樣的模式解決,便于后期維護(hù)和擴(kuò)展,確保最終在大規(guī)模協(xié)作的項(xiàng)目中達(dá)成既定目標(biāo)。

無(wú)獨(dú)有偶,筆者去年在公司里負(fù)責(zé)升級(jí)和制定研發(fā)流程、設(shè)計(jì)模板、設(shè)計(jì)標(biāo)準(zhǔn)、代碼標(biāo)準(zhǔn)等規(guī)范,并在實(shí)際工作中進(jìn)行了應(yīng)用和推廣,收效頗豐,也總結(jié)了適合支付平臺(tái)的技術(shù)規(guī)范,由于阿里巴巴Java開(kāi)發(fā)手冊(cè)本身定位為規(guī)約和規(guī)范,語(yǔ)言簡(jiǎn)單、精煉,沒(méi)有太多的解讀和示例,有些條款對(duì)于一般開(kāi)發(fā)人員理解起來(lái)比較困難,本文借著阿里巴巴發(fā)布的Java開(kāi)發(fā)手冊(cè),詳細(xì)解讀Java平臺(tái)下開(kāi)發(fā)規(guī)范和標(biāo)準(zhǔn)的制定和實(shí)施,強(qiáng)調(diào)那些在開(kāi)發(fā)過(guò)程中需要重點(diǎn)關(guān)注的技術(shù)點(diǎn),特別是解決某類(lèi)已識(shí)別問(wèn)題的模式和反模式。
《阿里巴巴Java開(kāi)發(fā)手冊(cè)》分為編程規(guī)約、異常日志、MySQL規(guī)約、工程規(guī)約、安全規(guī)約五大部分,本系列文章以這五部分主題為主線,分為五篇文章發(fā)布,本文為系列文章的第一篇-編程規(guī)約,后續(xù)會(huì)盡快發(fā)布其余的文章。
1 命名規(guī)約
1.【強(qiáng)制】 代碼中的命名均不能以下劃線或美元符號(hào)開(kāi)始,也不能以下劃線或美元符號(hào)結(jié)束。
反例: name / __name / $Object / name / name$ / Object$
白話:
這條不夠嚴(yán)格,普通的變量、類(lèi)名、方法名必須使用駝峰式命名,最好不要使用下劃線和美元符號(hào),否則看起來(lái)像腳本語(yǔ)言似得,常量可以使用下劃線,但是也不要放在常量開(kāi)頭和結(jié)尾。
2.【強(qiáng)制】 代碼中的命名嚴(yán)禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。
說(shuō)明: 正確的英文拼寫(xiě)和語(yǔ)法可以讓閱讀者易于理解,避免歧義。注意,即使純拼音命名方式 也要避免采用。
反例: DaZhePromotion [打折] / getPingfenByName() [評(píng)分] / int 某變量 = 3
正例: alibaba / taobao / youku / hangzhou 等國(guó)際通用的名稱,可視同英文。
白話:
中英混合的人種咱不歧視,變量名混合太丑了。
Java編譯器支持Unicode(UTF-8),允許中文命名變量,不過(guò)打中文還是沒(méi)有英文快。
英文!英文起名,洋氣、大方、高大上...
3.【強(qiáng)制】類(lèi)名使用 UpperCamelCase 風(fēng)格,必須遵從駝峰形式,但以下情形例外:(領(lǐng)域模型 的相關(guān)命名)DO / BO / DTO / VO等。
正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion
白話:
約定俗成的名稱或者縮寫(xiě)例外。
4.【強(qiáng)制】方法名、參數(shù)名、成員變量、局部變量都統(tǒng)一使用 lowerCamelCase 風(fēng)格,必須遵從駝峰形式。
正例: localValue / getHttpMessage() / inputUserId
白話:
約定俗稱的名稱或者縮寫(xiě)例外。
ID為簡(jiǎn)寫(xiě),Id和ID均可。
5.【強(qiáng)制】常量命名全部大寫(xiě),單詞間用下劃線隔開(kāi),力求語(yǔ)義表達(dá)完整清楚,不要嫌名字長(zhǎng)。
正例: MAX_STOCK_COUNT
反例: MAX_COUNT
白話:
必須全部大寫(xiě),除了字母數(shù)字只可以使用下劃線,并且不能用在開(kāi)頭和結(jié)尾。
6.【強(qiáng)制】抽象類(lèi)命名使用 Abstract 或 Base 開(kāi)頭;異常類(lèi)命名使用 Exception 結(jié)尾;測(cè)試類(lèi)命名以它要測(cè)試的類(lèi)的名稱開(kāi)始,以 Test 結(jié)尾。
白話:
家里放一瓶敵敵畏,上面不寫(xiě)標(biāo)簽,萬(wàn)一喝大了、渴了、喝了、就慘了,你懂的。
7.【強(qiáng)制】中括號(hào)是數(shù)組類(lèi)型的一部分,數(shù)組定義如下:String[] args;
反例: 使用String args[]的方式來(lái)定義。
白話:
這種語(yǔ)法編譯器也認(rèn),但是我們畢竟寫(xiě)Java程序,而不是寫(xiě)C/C++程序。
這怪Java編譯器小組,一開(kāi)始就不應(yīng)該支持這種語(yǔ)法。
8.【強(qiáng)制】POJO 類(lèi)中布爾類(lèi)型的變量,都不要加 is,否則部分框架解析會(huì)引起序列化錯(cuò)誤。
反例: 定義為基本數(shù)據(jù)類(lèi)型Boolean isSuccess;的屬性,它的方法也是isSuccess(),RPC 框架在反向解析的時(shí)候,“以為”對(duì)應(yīng)的屬性名稱是 success,導(dǎo)致屬性獲取不到,進(jìn)而拋出異常。
白話:
一些框架使用getter和setter做序列化,有的根據(jù)屬性本身取值,帶了is前綴就找不到了,變量名不要帶be動(dòng)詞,語(yǔ)法不對(duì),英文補(bǔ)考!
9.【強(qiáng)制】包名統(tǒng)一使用小寫(xiě),點(diǎn)分隔符之間有且僅有一個(gè)自然語(yǔ)義的英語(yǔ)單詞。包名統(tǒng)一使用 單數(shù)形式,但是類(lèi)名如果有復(fù)數(shù)含義,類(lèi)名可以使用復(fù)數(shù)形式。
正例: 應(yīng)用工具類(lèi)包名為com.alibaba.open.util、類(lèi)名為MessageUtils(此規(guī)則參考 spring 的框架結(jié)構(gòu))
白話:
包名大寫(xiě)、帶下劃線等,不專業(yè)、難看、不高大上。
10.【強(qiáng)制】杜絕完全不規(guī)范的縮寫(xiě),避免望文不知義。
反例: AbstractClass“縮寫(xiě)”命名成 AbsClass;condition“縮寫(xiě)”命名成 condi,此類(lèi) 隨意縮寫(xiě)嚴(yán)重降低了代碼的可閱讀性。
白話:
不要太摳,不是太長(zhǎng)的名字直接寫(xiě)上就好,編譯器編譯優(yōu)化后變量名將不存在,會(huì)編譯成相對(duì)于方法堆棧bp指針地址的相對(duì)地址,長(zhǎng)變量名不會(huì)占用更多空間。
英文中的縮寫(xiě)有個(gè)慣例,去掉元音留下輔音即可,不能亂縮寫(xiě)。
11.【推薦】如果使用到了設(shè)計(jì)模式,建議在類(lèi)名中體現(xiàn)出具體模式。
說(shuō)明: 將設(shè)計(jì)模式體現(xiàn)在名字中,有利于閱讀者快速理解架構(gòu)設(shè)計(jì)思想。
正例: public class OrderFactory;
- public class LoginProxy;
- public class ResourceObserver;
白話:
讓全世界都知道你會(huì)設(shè)計(jì)模式,這是個(gè)崇尚顯擺的社會(huì)。
12.【推薦】接口類(lèi)中的方法和屬性不要加任何修飾符號(hào)(public 也不要加),保持代碼的簡(jiǎn)潔 性,并加上有效的 Javadoc 注釋。盡量不要在接口里定義變量,如果一定要定義變量,肯定是與接口方法相關(guān),并且是整個(gè)應(yīng)用的基礎(chǔ)常量。
正例: 接口方法簽名:void f();
13.接口基礎(chǔ)常量表示:String COMPANY = "alibaba";
反例: 接口方法定義:public abstract void f();
說(shuō)明:JDK8 中接口允許有默認(rèn)實(shí)現(xiàn),那么這個(gè) default 方法,是對(duì)所有實(shí)現(xiàn)類(lèi)都有價(jià)值的默 認(rèn)實(shí)現(xiàn)。
白話:
脫了褲子放屁始終有點(diǎn)麻煩。
13.接口和實(shí)現(xiàn)類(lèi)的命名有兩套規(guī)則:
1)【強(qiáng)制】對(duì)于 Service 和 DAO 類(lèi),基于 SOA 的理念,暴露出來(lái)的服務(wù)一定是接口,內(nèi)部的實(shí)現(xiàn)類(lèi)用 Impl 的后綴與接口區(qū)別。
正例: CacheServiceImpl 實(shí)現(xiàn) CacheService 接口。
2)【推薦】 如果是形容能力的接口名稱,取對(duì)應(yīng)的形容詞做接口名(通常是–able 的形式)。
正例: AbstractTranslator 實(shí)現(xiàn) Translatable。
白話:
嚴(yán)重同意!可是想想Observer和Observable,我就不說(shuō)話了。
14.【參考】枚舉類(lèi)名建議帶上 Enum 后綴,枚舉成員名稱需要全大寫(xiě),單詞間用下劃線隔開(kāi)。
說(shuō)明: 枚舉其實(shí)就是特殊的常量類(lèi),且構(gòu)造方法被默認(rèn)強(qiáng)制是私有。
正例: 枚舉名字:DealStatusEnum,成員名稱: SUCCESS / UNKOWN_REASON。
白話:
不要駝峰!記住枚舉不要駝峰!總是有好多人枚舉用駝峰。
15.【參考】各層命名規(guī)約:
A) Service/DAO層方法命名規(guī)約
1) 獲取單個(gè)對(duì)象的方法用get做前綴。
2) 獲取多個(gè)對(duì)象的方法用list做前綴。
3) 獲取統(tǒng)計(jì)值的方法用count做前綴。
4) 插入的方法用save(推薦)或insert做前綴。
5) 刪除的方法用remove(推薦)或delete做前綴。
6) 修改的方法用update做前綴。
B) 領(lǐng)域模型命名規(guī)約
1) 數(shù)據(jù)對(duì)象:xxxDO,xxx即為數(shù)據(jù)表名。
2) 數(shù)據(jù)傳輸對(duì)象:xxxDTO,xxx為業(yè)務(wù)領(lǐng)域相關(guān)的名稱。
3) 展示對(duì)象:xxxVO,xxx一般為網(wǎng)頁(yè)名稱。
4) POJO是DO/DTO/BO/VO的統(tǒng)稱,禁止命名成xxxPOJO。
白話:
大家都這么認(rèn)為很重要。
2 常量定義
1.【強(qiáng)制】不允許出現(xiàn)任何魔法值(即未經(jīng)定義的常量)直接出現(xiàn)在代碼中。
反例: String key = "Id#taobao_"+tradeId; cache.put(key, value);
白話:
這個(gè)不用說(shuō)了,隨地吐痰和隨地大小便是不應(yīng)該的,新加坡是要鞭刑的!
2.【強(qiáng)制】long 或者 Long 初始賦值時(shí),必須使用大寫(xiě)的 L,不能是小寫(xiě)的 l,小寫(xiě)容易跟數(shù)字 1 混淆,造成誤解。
說(shuō)明: Long a = 2l; 寫(xiě)的是數(shù)字的21,還是Long型的2?
白話:
看看區(qū)塊鏈中用了base58,而不是base64,秒懂什么是從用戶角度考慮產(chǎn)品設(shè)計(jì)!
3.【推薦】不要使用一個(gè)常量類(lèi)維護(hù)所有常量,應(yīng)該按常量功能進(jìn)行歸類(lèi),分開(kāi)維護(hù)。如:緩存相關(guān)的常量放在類(lèi): CacheConsts 下; 系統(tǒng)配置相關(guān)的常量放在類(lèi): ConfigConsts 下。
說(shuō)明: 大而全的常量類(lèi),非得使用查找功能才能定位到修改的常量,不利于理解和維護(hù)。
白話:
盡量讓功能自閉包,標(biāo)準(zhǔn)是一個(gè)小模塊拷貝出去直接就能用,而不是缺這缺那的,是不是讀者很多時(shí)候拷貝了一套類(lèi),運(yùn)行時(shí)候發(fā)現(xiàn)不能用,缺常量,把常量類(lèi)拷貝過(guò)來(lái),發(fā)現(xiàn)常量類(lèi)中有很多不相關(guān)的常量,還得清理。
4.【推薦】常量的復(fù)用層次有五層: 跨應(yīng)用共享常量、應(yīng)用內(nèi)共享常量、子工程內(nèi)共享常量、包內(nèi)共享常量、類(lèi)內(nèi)共享常量。
1) 跨應(yīng)用共享常量: 放置在二方庫(kù)中,通常是client.jar中的constant目錄下。
2) 應(yīng)用內(nèi)共享常量: 放置在一方庫(kù)的modules中的constant目錄下。
反例: 易懂變量也要統(tǒng)一定義成應(yīng)用內(nèi)共享常量,兩位攻城師在兩個(gè)類(lèi)中分別定義了表示 “是”的變量:
類(lèi)A中: public static final String YES = "yes";
類(lèi)B中: public static final String YES = "y"; A.YES.equals(B.YES),預(yù)期是 true,但實(shí)際返回為 false,導(dǎo)致產(chǎn)生線上問(wèn)題。
3) 子工程內(nèi)部共享常量: 即在當(dāng)前子工程的constant目錄下。
4) 包內(nèi)共享常量: 即在當(dāng)前包下單獨(dú)的constant目錄下。
5) 類(lèi)內(nèi)共享常量: 直接在類(lèi)內(nèi)部private static final定義。
白話:
一方庫(kù)、二方庫(kù)、三方庫(kù),叫法很專業(yè),放在離自己最近的上面一個(gè)層次即可。
5.【推薦】如果變量值僅在一個(gè)范圍內(nèi)變化用 Enum 類(lèi)。如果還帶有名稱之外的延伸屬性,必須 使用 Enum 類(lèi),下面正例中的數(shù)字就是延伸信息,表示星期幾。
正例: public Enum { MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6), SUNDAY(7);}
白話:
枚舉值需要定義延伸屬性的場(chǎng)景通常是要持久數(shù)據(jù)庫(kù),或者顯示在界面上。
3 格式規(guī)約
1.【強(qiáng)制】大括號(hào)的使用約定。如果是大括號(hào)內(nèi)為空,則簡(jiǎn)潔地寫(xiě)成{}即可,不需要換行; 如果 是非空代碼塊則:
1) 左大括號(hào)前不換行。
2) 左大括號(hào)后換行。
3) 右大括號(hào)前換行。
4) 右大括號(hào)后還有else等代碼則不換行;表示終止右大括號(hào)后必須換行。
白話:
好風(fēng)格,討厭那種左大括號(hào)前換行的,看不慣。
2.【強(qiáng)制】 左括號(hào)和后一個(gè)字符之間不出現(xiàn)空格; 同樣,右括號(hào)和前一個(gè)字符之間也不出現(xiàn)空 格。詳見(jiàn)第 5 條下方正例提示。
白話:
程序?qū)懲昕梢杂镁庉嬈鞯母袷交δ芨袷交珽clipse中快捷鍵是shift+alt+f,筆者寫(xiě)程序的時(shí)候有個(gè)習(xí)慣,每次謝了一段代碼都會(huì)按ctrl+alt+o、ctrl+alt+f、ctrl+s,相信會(huì)有相同習(xí)慣的同行。
3.【強(qiáng)制】if/for/while/switch/do 等保留字與左右括號(hào)之間都必須加空格。
白話:
程序?qū)懲昕梢杂镁庉嬈鞯母袷交δ芨袷交?,Eclipse中快捷鍵是shift+alt+f,筆者寫(xiě)程序的時(shí)候有個(gè)習(xí)慣,每次謝了一段代碼都會(huì)按ctrl+alt+o、ctrl+alt+f、ctrl+s,相信會(huì)有相同習(xí)慣的同行。
4.【強(qiáng)制】任何運(yùn)算符左右必須加一個(gè)空格。
說(shuō)明: 運(yùn)算符包括賦值運(yùn)算符=、邏輯運(yùn)算符&&、加減乘除符號(hào)、三目運(yùn)算符等。
白話:
程序?qū)懲昕梢杂镁庉嬈鞯母袷交δ芨袷交?,Eclipse中快捷鍵是shift+alt+f,筆者寫(xiě)程序的時(shí)候有個(gè)習(xí)慣,每次謝了一段代碼都會(huì)按ctrl+alt+o、ctrl+alt+f、ctrl+s,相信會(huì)有相同習(xí)慣的同行。
5.【強(qiáng)制】縮進(jìn)采用 4 個(gè)空格,禁止使用 tab 字符。
說(shuō)明: 如果使用 tab 縮進(jìn),必須設(shè)置 1 個(gè) tab 為 4 個(gè)空格。IDEA 設(shè)置 tab 為 4 個(gè)空格時(shí),請(qǐng)勿勾選Use tab character; 而在 eclipse 中,必須勾選 insert spaces for tabs。
正例: (涉及1-5點(diǎn))
- public static void main(String[] args) {
- // 縮進(jìn) 4 個(gè)空格
- String say = "hello";
- // 運(yùn)算符的左右必須有一個(gè)空格
- int flag = 0;
- // 關(guān)鍵詞 if 與括號(hào)之間必須有一個(gè)空格,括號(hào)內(nèi)的 f 與左括號(hào),0 與右括號(hào)不需要空格
- if (flag == 0) {
- System.out.println(say);
- }
- // 左大括號(hào)前加空格且不換行;左大括號(hào)后換行
- if (flag == 1) {
- System.out.println("world");
- // 右大括號(hào)前換行,右大括號(hào)后有 else,不用換行
- } else { System.out.println("ok");
- // 在右大括號(hào)后直接結(jié)束,則必須換行
- }
- }
白話:
這樣看慣了,怎么看怎么清晰。
6.【強(qiáng)制】單行字符數(shù)限制不超過(guò) 120 個(gè),超出需要換行,換行時(shí)遵循如下原則:
1) 第二行相對(duì)第一行縮進(jìn) 4 個(gè)空格,從第三行開(kāi)始,不再繼續(xù)縮進(jìn),參考示例。
2) 運(yùn)算符與下文一起換行。
3) 方法調(diào)用的點(diǎn)符號(hào)與下文一起換行。
4) 在多個(gè)參數(shù)超長(zhǎng),逗號(hào)后進(jìn)行換行。
5) 在括號(hào)前不要換行,見(jiàn)反例。
正例:
- StringBuffer sb = new StringBuffer();
- //超過(guò) 120 個(gè)字符的情況下,換行縮進(jìn) 4 個(gè)空格,并且方法前的點(diǎn)符號(hào)一起換行
- sb.append("zi").append("xin")...
- .append("huang")...
- .append("huang")...
- .append("huang");
反例:
- StringBuffer sb = new StringBuffer();
- //超過(guò) 120 個(gè)字符的情況下,不要在括號(hào)前換行
- sb.append("zi").append("xin")...append
- ("huang");
- //參數(shù)很多的方法調(diào)用可能超過(guò) 120 個(gè)字符,不要在逗號(hào)前換行
- method(args1, args2, args3, ...
- , argsX);
白話:
一行代碼盡量不要寫(xiě)太長(zhǎng),長(zhǎng)了拆開(kāi)不就得了。
7.【強(qiáng)制】方法參數(shù)在定義和傳入時(shí),多個(gè)參數(shù)逗號(hào)后邊必須加空格。
正例: 下例中實(shí)參的"a", 后邊必須要有一個(gè)空格。
- method("a", "b", "c");
白話:
不加空格太擠了,就像人沒(méi)長(zhǎng)開(kāi)似得。
8.【強(qiáng)制】IDE的text file encoding設(shè)置為UTF-8; IDE中文件的換行符使用Unix格式, 不要使用 windows 格式。
白話:
請(qǐng)不要用GB字符集,換了環(huán)境總有問(wèn)題,Java程序多數(shù)跑在Linux上,當(dāng)然要用Unix換行符。
9.【推薦】沒(méi)有必要增加若干空格來(lái)使某一行的字符與上一行的相應(yīng)字符對(duì)齊。
正例:
- int a = 3;
- long b = 4L;
- float c = 5F;
- StringBuffer sb = new StringBuffer();
說(shuō)明: 增加 sb 這個(gè)變量,如果需要對(duì)齊,則給 a、b、c 都要增加幾個(gè)空格,在變量比較多的 情況下,是一種累贅的事情。
白話:
沒(méi)必要,沒(méi)必要,那樣反而不好看。
10.【推薦】方法體內(nèi)的執(zhí)行語(yǔ)句組、變量的定義語(yǔ)句組、不同的業(yè)務(wù)邏輯之間或者不同的語(yǔ)義
之間插入一個(gè)空行。相同業(yè)務(wù)邏輯和語(yǔ)義之間不需要插入空行。
說(shuō)明: 沒(méi)有必要插入多行空格進(jìn)行隔開(kāi)。
白話:
和我的習(xí)慣一樣一樣的,一段邏輯空一行。
4 OOP 規(guī)約
1.【強(qiáng)制】避免通過(guò)一個(gè)類(lèi)的對(duì)象引用訪問(wèn)此類(lèi)的靜態(tài)變量或靜態(tài)方法,無(wú)謂增加編譯器解析成
本,直接用類(lèi)名來(lái)訪問(wèn)即可。
白話:
也不直觀,看調(diào)用代碼看不出來(lái)是靜態(tài)方法,容易誤解。
2.【強(qiáng)制】所有的覆寫(xiě)方法,必須加@Override 注解。
反例:getObject()與 get0bject()的問(wèn)題。一個(gè)是字母的 O,一個(gè)是數(shù)字的 0,加@Override 可以準(zhǔn)確判斷是否覆蓋成功。另外,如果在抽象類(lèi)中對(duì)方法簽名進(jìn)行修改,其實(shí)現(xiàn)類(lèi)會(huì)馬上編譯報(bào)錯(cuò)。
白話:
Java和C++不一樣,C++是在父類(lèi)先聲明虛擬函數(shù)子類(lèi)才覆寫(xiě),Java是任何方法都能覆寫(xiě),也可以不覆寫(xiě),所以覆寫(xiě)不覆寫(xiě)是沒(méi)有編譯器檢查的,除非接口中某一個(gè)方法完全沒(méi)有被實(shí)現(xiàn)才會(huì)編譯報(bào)錯(cuò)。
3.【強(qiáng)制】相同參數(shù)類(lèi)型,相同業(yè)務(wù)含義,才可以使用 Java 的可變參數(shù),避免使用 Object。
說(shuō)明: 可變參數(shù)必須放置在參數(shù)列表的最后。(提倡同學(xué)們盡量不用可變參數(shù)編程)
正例: public User getUsers(String type, Integer... ids)
白話:
用處不大,可以用重載方法或者數(shù)組參數(shù)代替。
一般應(yīng)用在日志的 API 定義上,用于傳不定的日志參數(shù)。
4.【強(qiáng)制】外部正在調(diào)用或者二方庫(kù)依賴的接口,不允許修改方法簽名,避免對(duì)接口調(diào)用方產(chǎn)生 影響。接口過(guò)時(shí)必須加@Deprecated 注解,并清晰地說(shuō)明采用的新接口或者新服務(wù)是什么。
白話:
設(shè)計(jì)時(shí)沒(méi)有考慮周全,需要改造接口,需要通過(guò)增加新接口,遷移后下線老接口的方式實(shí)現(xiàn)。
REST接口只能增加參數(shù),不能減少參數(shù),返回值的內(nèi)容也是只增不減。
5.【強(qiáng)制】不能使用過(guò)時(shí)的類(lèi)或方法。
說(shuō)明: java.net.URLDecoder 中的方法 decode(String encodeStr) 這個(gè)方法已經(jīng)過(guò)時(shí),應(yīng)該使用雙參數(shù) decode(String source, String encode)。接口提供方既然明確是過(guò)時(shí)接口,那么有義務(wù)同時(shí)提供新的接口; 作為調(diào)用方來(lái)說(shuō),有義務(wù)去考證過(guò)時(shí)方法的新實(shí)現(xiàn)是什么。
白話:
明確了責(zé)任和義務(wù),接口提供方也有義務(wù)推動(dòng)接口使用方盡早遷移,不要積累技術(shù)負(fù)債。
6.【強(qiáng)制】Object 的 equals 方法容易拋空指針異常,應(yīng)使用常量或確定有值的對(duì)象來(lái)調(diào)用 equals。
正例: "test".equals(object);
反例: object.equals("test");
說(shuō)明: 推薦使用java.util.Objects#equals (JDK7引入的工具類(lèi))
白話:
常量比變量,永遠(yuǎn)都不變的原則。
7.【強(qiáng)制】所有的相同類(lèi)型的包裝類(lèi)對(duì)象之間值的比較,全部使用 equals 方法比較。
說(shuō)明: 對(duì)于Integer var = ?在-128至127之間的賦值,Integer對(duì)象是在 IntegerCache.cache 產(chǎn)生,會(huì)復(fù)用已有對(duì)象,這個(gè)區(qū)間內(nèi)的 Integer 值可以直接使用==進(jìn)行 判斷,但是這個(gè)區(qū)間之外的所有數(shù)據(jù),都會(huì)在堆上產(chǎn)生,并不會(huì)復(fù)用已有對(duì)象,這是一個(gè)大坑, 推薦使用 equals 方法進(jìn)行判斷。
白話:
Java世界里相等請(qǐng)用equals方法,==表示對(duì)象相等,一般在框架開(kāi)發(fā)中會(huì)用到。
關(guān)于基本數(shù)據(jù)類(lèi)型與包裝數(shù)據(jù)類(lèi)型的使用標(biāo)準(zhǔn)如下:
1) 【強(qiáng)制】所有的POJO類(lèi)屬性必須使用包裝數(shù)據(jù)類(lèi)型。
2) 【強(qiáng)制】RPC方法的返回值和參數(shù)必須使用包裝數(shù)據(jù)類(lèi)型。
3) 【推薦】所有的局部變量使用基本數(shù)據(jù)類(lèi)型。
說(shuō)明: POJO 類(lèi)屬性沒(méi)有初值是提醒使用者在需要使用時(shí),必須自己顯式地進(jìn)行賦值,任何
NPE 問(wèn)題,或者入庫(kù)檢查,都由使用者來(lái)保證。
正例: 數(shù)據(jù)庫(kù)的查詢結(jié)果可能是 null,因?yàn)樽詣?dòng)拆箱,用基本數(shù)據(jù)類(lèi)型接收有 NPE 風(fēng)險(xiǎn)。
反例: 比如顯示成交總額漲跌情況,即正負(fù) x%,x 為基本數(shù)據(jù)類(lèi)型,調(diào)用的 RPC 服務(wù),調(diào)用不成功時(shí),返回的是默認(rèn)值,頁(yè)面顯示:0%,這是不合理的,應(yīng)該顯示成中劃線-。所以包裝數(shù)據(jù)類(lèi)型的 null 值,能夠表示額外的信息,如:遠(yuǎn)程調(diào)用失敗,異常退出。
白話:
其實(shí)包裝數(shù)據(jù)類(lèi)型與基本數(shù)據(jù)類(lèi)型相比,增加了一個(gè)null的狀態(tài),可以攜帶更多的語(yǔ)義。
9.【強(qiáng)制】定義 DO/DTO/VO 等 POJO 類(lèi)時(shí),不要設(shè)定任何屬性默認(rèn)值。
反例: POJO類(lèi)的gmtCreate默認(rèn)值為new Date(); 但是這個(gè)屬性在數(shù)據(jù)提取時(shí)并沒(méi)有置入具體值,在更新其它字段時(shí)又附帶更新了此字段,導(dǎo)致創(chuàng)建時(shí)間被修改成當(dāng)前時(shí)間。
白話:
雖然這里反例不太容易看懂,但是要記得持久領(lǐng)域?qū)ο笾坝蓱?yīng)用層統(tǒng)一賦值gmtCreate和gmtModify字段。
10.【強(qiáng)制】序列化類(lèi)新增屬性時(shí),請(qǐng)不要修改 serialVersionUID 字段,避免反序列失敗; 如 果完全不兼容升級(jí),避免反序列化混亂,那么請(qǐng)修改 serialVersionUID 值。
說(shuō)明:注意 serialVersionUID 不一致會(huì)拋出序列化運(yùn)行時(shí)異常。
白話:
不到萬(wàn)不得已不要使用JDK自身的序列化,機(jī)制很重,信息冗余有版本。
11.【強(qiáng)制】構(gòu)造方法里面禁止加入任何業(yè)務(wù)邏輯,如果有初始化邏輯,請(qǐng)放在 init 方法中。
白話:
這樣做一種是規(guī)范,代碼清晰,還有就是異常堆棧上更容易識(shí)別出錯(cuò)的方法和語(yǔ)句。
12.【強(qiáng)制】POJO 類(lèi)必須寫(xiě) toString 方法。使用 IDE 的中工具:source> generate toString 時(shí),如果繼承了另一個(gè) POJO 類(lèi),注意在前面加一下 super.toString。
說(shuō)明: 在方法執(zhí)行拋出異常時(shí),可以直接調(diào)用 POJO 的 toString()方法打印其屬性值,便于排 查問(wèn)題。
白話:
這里還有一個(gè)大坑,寫(xiě)toString的時(shí)候要保證不會(huì)發(fā)生NPE,有的時(shí)候toString調(diào)用實(shí)例變量的toString,實(shí)例變量由于某些原因?yàn)閚ull,導(dǎo)致NPE,代碼沒(méi)有處理好就終止,這個(gè)問(wèn)題坑了好多次。
13.【推薦】使用索引訪問(wèn)用 String 的 split 方法得到的數(shù)組時(shí),需做最后一個(gè)分隔符后有無(wú)內(nèi)容的檢查,否則會(huì)有拋 IndexOutOfBoundsException 的風(fēng)險(xiǎn)。
說(shuō)明:
- String str = "a,b,c,,";
- String[] ary = str.split(","); //預(yù)期大于 3,結(jié)果是 3
- System.out.println(ary.length);
白話:
編程要留心眼,任何不確定的地方都要判斷、處理,否則掉到坑里了自己爬出來(lái)很費(fèi)勁。
Java編程判空的思想要實(shí)施縈繞在每個(gè)開(kāi)發(fā)人員的腦海里。
14.【推薦】當(dāng)一個(gè)類(lèi)有多個(gè)構(gòu)造方法,或者多個(gè)同名方法,這些方法應(yīng)該按順序放置在一起, 便于閱讀。
白話:
這規(guī)范說(shuō)的咋就和我的習(xí)慣一模一樣呢!
1.【推薦】 類(lèi)內(nèi)方法定義順序依次是: 公有方法或保護(hù)方法 > 私有方法 > getter/setter
方法。
說(shuō)明: 公有方法是類(lèi)的調(diào)用者和維護(hù)者最關(guān)心的方法,首屏展示最好; 保護(hù)方法雖然只是子類(lèi) 關(guān)心,也可能是“模板設(shè)計(jì)模式”下的核心方法; 而私有方法外部一般不需要特別關(guān)心,是一個(gè)黑盒實(shí)現(xiàn); 因?yàn)榉椒ㄐ畔r(jià)值較低,所有 Service 和 DAO 的 getter/setter 方法放在類(lèi)體最 后。
白話:
我推薦把一套邏輯的共有方法、保護(hù)方法、私有方法放在一起,所有g(shù)etter/setter放在最后,這樣感覺(jué)更有邏輯!
2.【推薦】setter 方法中,參數(shù)名稱與類(lèi)成員變量名稱一致,this.成員名 = 參數(shù)名。在
getter/setter 方法中,盡量不要增加業(yè)務(wù)邏輯,增加排查問(wèn)題的難度。
反例:
- public Integer getData() {
- if (true) {
- return data + 100;
- } else {
- return data - 100; }
- }
白話:
雙手贊成。
3.【推薦】循環(huán)體內(nèi),字符串的連接方式,使用 StringBuilder 的 append 方法進(jìn)行擴(kuò)展。
反例:
- String str = "start";
- for (int I = 0; I < 100; i++) {
- str = str + "hello";
- }
說(shuō)明: 反編譯出的字節(jié)碼文件顯示每次循環(huán)都會(huì) new 出一個(gè) StringBuilder 對(duì)象,然后進(jìn)行 append 操作,最后通過(guò) toString 方法返回 String 對(duì)象,造成內(nèi)存資源浪費(fèi)。
白話:
一定使用StringBuilder,不要使用StringBuffer,StringBuffer是線程安全的,太重。
我就一直想不明白Java編譯器為什么不做個(gè)優(yōu)化呢?
4.【推薦】下列情況,聲明成 final 會(huì)更有提示性:
1) 不需要重新賦值的變量,包括類(lèi)屬性、局部變量。
2) 對(duì)象參數(shù)前加final,表示不允許修改引用的指向。
3) 類(lèi)方法確定不允許被重寫(xiě)。
白話:
盡量多使用final關(guān)鍵字,保證編譯器的校驗(yàn)機(jī)制起作用,也體現(xiàn)了“契約式編程”的思想。
5.【推薦】慎用 Object 的 clone 方法來(lái)拷貝對(duì)象。
說(shuō)明: 對(duì)象的 clone 方法默認(rèn)是淺拷貝,若想實(shí)現(xiàn)深拷貝需要重寫(xiě) clone 方法實(shí)現(xiàn)屬性對(duì)象的拷貝。
白話:
最好是使用構(gòu)造函數(shù)來(lái)重新構(gòu)造對(duì)象,使用clone淺拷貝的時(shí)候,對(duì)象引用關(guān)系可能很復(fù)雜,不直觀,不好理解。
6.【推薦】類(lèi)成員與方法訪問(wèn)控制從嚴(yán):
1) 如果不允許外部直接通過(guò)new來(lái)創(chuàng)建對(duì)象,那么構(gòu)造方法必須是private。
2) 工具類(lèi)不允許有public或default構(gòu)造方法。
3) 類(lèi)非static成員變量并且與子類(lèi)共享,必須是protected。
4) 類(lèi)非static成員變量并且僅在本類(lèi)使用,必須是private。
5) 類(lèi)static成員變量如果僅在本類(lèi)使用,必須是private。
6) 若是static成員變量,必須考慮是否為final。
7) 類(lèi)成員方法只供類(lèi)內(nèi)部調(diào)用,必須是private。
8) 類(lèi)成員方法只對(duì)繼承類(lèi)公開(kāi),那么限制為protected。
說(shuō)明: 任何類(lèi)、方法、參數(shù)、變量,嚴(yán)控訪問(wèn)范圍。過(guò)寬泛的訪問(wèn)范圍,不利于模塊解耦。
思考: 如果是一個(gè) private 的方法,想刪除就刪除,可是一個(gè) public 的 Service 方法,或者一個(gè) public 的成員變量,刪除一下,不得手心冒點(diǎn)汗嗎?變量像自己的小孩,盡量在自己的視 線內(nèi),變量作用域太大,如果無(wú)限制的到處跑,那么你會(huì)擔(dān)心的。
白話:
沒(méi)什么好說(shuō)的,兩個(gè)詞,高內(nèi)聚,低耦合,功能模塊閉包,哦,是三個(gè)詞。
5 集合處理
1.【強(qiáng)制】關(guān)于 hashCode 和 equals 的處理,遵循如下規(guī)則:
1) 只要重寫(xiě)equals,就必須重寫(xiě)hashCode。
2) 因?yàn)镾et存儲(chǔ)的是不重復(fù)的對(duì)象,依據(jù)hashCode和equals進(jìn)行判斷,所以Set存儲(chǔ)的對(duì)象必須重寫(xiě)這兩個(gè)方法。
3) 如果自定義對(duì)象做為Map的鍵,那么必須重寫(xiě)hashCode和equals。
說(shuō)明: String 重寫(xiě)了 hashCode 和 equals 方法,所以我們可以非常愉快地使用 String 對(duì)象作為 key 來(lái)使用。
白話:
Hash是個(gè)永恒的話題,大家可以看下times33和Murmurhash算法。
2.【強(qiáng)制】ArrayList的subList結(jié)果不可強(qiáng)轉(zhuǎn)成ArrayList,否則會(huì)拋出ClassCastException
異常: java.util.RandomAccessSubList cannot be cast to java.util.ArrayList ;
說(shuō)明: subList 返回的是 ArrayList 的內(nèi)部類(lèi) SubList,并不是 ArrayList ,而是 ArrayList 的一個(gè)視圖,對(duì)于SubList子列表的所有操作最終會(huì)反映到原列表上。
白話:
這種問(wèn)題本來(lái)測(cè)試可以測(cè)試到,但是開(kāi)發(fā)永遠(yuǎn)都不要有依賴測(cè)試的想法,一切靠自己,當(dāng)然我們的測(cè)試人員都是很靠譜的。
3.【強(qiáng)制】 在 subList 場(chǎng)景中,高度注意對(duì)原集合元素個(gè)數(shù)的修改,會(huì)導(dǎo)致子列表的遍歷、增 加、刪除均產(chǎn)生ConcurrentModificationException 異常。
白話:
如果一定要更改子列表,重新構(gòu)造新的ArrayList,使用
- public ArrayList(Collection extends E> c)
4.【強(qiáng)制】使用集合轉(zhuǎn)數(shù)組的方法,必須使用集合的toArray(T[] array),傳入的是類(lèi)型完全 一樣的數(shù)組,大小就是 list.size()。
說(shuō)明: 使用 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ù)一致。
正例:
- List
list = new ArrayList (2); list.add("guan"); - list.add("bao");
- String[] array = new String[list.size()];
- array = list.toArray(array);
反例: 直接使用 toArray 無(wú)參方法存在問(wèn)題,此方法返回值只能是 Object[]類(lèi),若強(qiáng)轉(zhuǎn)其它類(lèi)型數(shù)組將出現(xiàn) ClassCastException 錯(cuò)誤。
白話:
搞不懂Java編譯器為什么不做優(yōu)化,人用邏輯能推導(dǎo)的,程序一定可以自動(dòng)實(shí)現(xiàn)。
5.【強(qiáng)制】使用工具類(lèi) Arrays.asList()把數(shù)組轉(zhuǎn)換成集合時(shí),不能使用其修改集合相關(guān)的方 法,它的 add/remove/clear 方法會(huì)拋出 UnsupportedOperationException 異常。
說(shuō)明: asList 的返回對(duì)象是一個(gè) Arrays 內(nèi)部類(lèi),并沒(mé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ì)隨之修改。
白話:
如果需要對(duì)asList返回的List做更改,可以構(gòu)造新的ArrayList,使用public ArrayList(Collection extends E> c)構(gòu)造器。
6.【強(qiáng)制】泛型通配符 extends T>來(lái)接收返回的數(shù)據(jù),此寫(xiě)法的泛型集合不能使用add方 法,而 super T>不能使用get方法,做為接口調(diào)用賦值時(shí)易出錯(cuò)。
說(shuō)明: 擴(kuò)展說(shuō)一下PECS(Producer Extends Consumer Super)原則: 1)頻繁往外讀取內(nèi)容的,適合用上界 Extends。 2)經(jīng)常往里插入的,適合用下界 Super。
白話:
extends T>, ? 必須是T或T的子類(lèi)
集合寫(xiě)(add): 因?yàn)椴荒艽_定集合實(shí)例化時(shí)用的是T或T的子類(lèi),所以沒(méi)有辦法寫(xiě)。例如:List extends Number> foo = new ArrayList
集合讀(get): 只能讀出T類(lèi)型的數(shù)據(jù)。
super T>, ? 必須是T或T的父類(lèi)
集合寫(xiě)(add): 可以add T或T的子類(lèi)。
集合讀(get): 不能確定從集合里讀出的是哪個(gè)類(lèi)型(可能是T也可能是T的父類(lèi),或者Object),所以沒(méi)有辦法使用get。例如:List super Integer> foo3 = new ArrayList
下面是示例,test1和test2在編譯時(shí)都有錯(cuò)誤提示。
- package com.robert.javaspec;
- import java.util.LinkedList;
- import java.util.List;
- /**
- * Created by WangMeng on 2017-04-13.
- * FIX ME
- */
- public class Main {
- public static void main(String[] args) {
- }
- public void test1(){
- List extends A> childofa=new LinkedList<>();
- B b=new B();
- A a=new A();
- childofa.add(a);
- childofa.add(b);
- A ta= childofa.get(0);
- }
- public void test2(){
- List super B> superOfb = new LinkedList<>();
- B b = new B();
- A a = new A();
- superOfb.add(a);
- superOfb.add(b);
- A ta = superOfb.get(0);
- B tb = superOfb.get(0);
- }
- }
- class A {
- @Override
- public String toString() {
- return "A";
- }
- }
- class B extends A {
- @Override
- public String toString() {
- return "B";
- }
- }
7.【強(qiáng)制】不要在 foreach 循環(huán)里進(jìn)行元素的 remove/add 操作。remove 元素請(qǐng)使用 Iterator 方式,如果并發(fā)操作,需要對(duì) Iterator 對(duì)象加鎖。
反例:
- List
a = new ArrayList (); - a.add("1");
- a.add("2");
- for (String temp : a) {
- if ("1".equals(temp)) {
- a.remove(temp);
- }
- }
說(shuō)明: 以上代碼的執(zhí)行結(jié)果肯定會(huì)出乎大家的意料,那么試一下把“1”換成“2”,會(huì)是同樣的
結(jié)果嗎?
正例:
- Iterator
it = a.iterator(); - while (it.hasNext()) {
- String temp = it.next();
- if (刪除元素的條件) {
- it.remove();
- }
- }
白話:
修改一定要使用Iterator。
反例中改成2,拋出ConcurrentModificationException,因?yàn)?是數(shù)組的結(jié)束邊界。
8.【強(qiáng)制】 在 JDK7 版本及以上,Comparator 要滿足如下三個(gè)條件,不然 Arrays.sort, Collections.sort 會(huì)報(bào) IllegalArgumentException 異常。
說(shuō)明:
- 1) x,y的比較結(jié)果和y,x的比較結(jié)果相反。
- 2) x>y,y>z,則x>z。
- 3) x=y,則x,z比較結(jié)果和y,z比較結(jié)果相同。
反例: 下例中沒(méi)有處理相等的情況,實(shí)際使用中可能會(huì)出現(xiàn)異常:
- new Comparator
() { - @Override
- public int compare(Student o1, Student o2) {
- return o1.getId() > o2.getId() ? 1 : -1;
- }
- }
白話:
除非邏輯混亂,否則這些條件都能滿足。
9.【推薦】集合初始化時(shí),盡量指定集合初始值大小。
說(shuō)明: ArrayList盡量使用ArrayList(int initialCapacity) 初始化。
白話:
預(yù)估數(shù)組大小,能夠提高程序效率,寫(xiě)代碼的時(shí)候腦袋里面要有運(yùn)行的思想。
想了解性能和容量評(píng)估,請(qǐng)參考互聯(lián)網(wǎng)性能與容量評(píng)估的方法論和典型案例。
10.【推薦】使用 entrySet 遍歷 Map 類(lèi)集合 KV,而不是 keySet 方式進(jìn)行遍歷。
說(shuō)明: keySet 其實(shí)是遍歷了 2 次,一次是轉(zhuǎn)為 Iterator 對(duì)象,另一次是從 hashMap 中取出 key 所對(duì)應(yīng)的 value。而 entrySet 只是遍歷了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.foreach 方法。
正例: values()返回的是 V 值集合,是一個(gè) list 集合對(duì)象;keySet()返回的是 K 值集合,是一個(gè) Set 集合對(duì)象; entrySet()返回的是 K-V 值組合集合。
白話:
寫(xiě)代碼其實(shí)就是在程序員腦袋里執(zhí)行代碼的過(guò)程,直覺(jué)就是兩次肯定不如一次做完事更快。
11.【推薦】高度注意 Map 類(lèi)集合 K/V 能不能存儲(chǔ) null 值的情況,如下表格:
集合對(duì)照表
反例: 由于 HashMap 的干擾,很多人認(rèn)為 ConcurrentHashMap 是可以置入 null 值,注意存儲(chǔ) null 值時(shí)會(huì)拋出 NPE 異常。
白話:
存儲(chǔ)null值場(chǎng)景不多,在防止緩存穿透的情況下,有的時(shí)候會(huì)緩存null key
12.【參考】合理利用好集合的有序性(sort)和穩(wěn)定性(order),避免集合的無(wú)序性(unsort)和 不穩(wěn)定性(unorder)帶來(lái)的負(fù)面影響。
說(shuō)明: 有序性是指遍歷的結(jié)果是按某種比較規(guī)則依次排列的。穩(wěn)定性指集合每次遍歷的元素次 序是一定的。如:ArrayList 是 order/unsort;HashMap 是 unorder/unsort;TreeSet 是 order/sort。
白話:
大體上同意,但是對(duì)于HashMap理論上是無(wú)序的沒(méi)有問(wèn)題,我做了個(gè)試驗(yàn),每次輸出都是穩(wěn)定的。
數(shù)值:
- HashMap
map = new HashMap (); - map.put(3, 3);
- map.put(1, 1);
- map.put(2, 2);
- map.put(4, 4);
- for (Entry
entry : map.entrySet()) { - System.out.println(entry.getKey());
- }
事實(shí)證明,每次輸出也是1、2、3、4,有序并且穩(wěn)定的。
字符串值:
- HashMap
map = new HashMap (); - map.put("3000", "3");
- map.put("1000", "1");
- map.put("2000", "2");
- map.put("4000", "4");
- for (Entry
entry : map.entrySet()) { - System.out.println(entry.getKey());
- }
事實(shí)證明,每次輸出也是4000、1000、2000、3000,無(wú)序但是穩(wěn)定的。
與阿里專家咨詢,這里HashMap不穩(wěn)定性是指rehash后輸出順序則會(huì)變化。
13.【參考】利用 Set 元素唯一的特性,可以快速對(duì)一個(gè)集合進(jìn)行去重操作,避免使用 List 的 contains 方法進(jìn)行遍歷、對(duì)比、去重操作。
白話:
如果不需要精確去重,參考布隆過(guò)濾器(Bloom Filter)。
6 并發(fā)處理
1.【強(qiáng)制】獲取單例對(duì)象需要保證線程安全,其中的方法也要保證線程安全。
說(shuō)明: 資源驅(qū)動(dòng)類(lèi)、工具類(lèi)、單例工廠類(lèi)都需要注意。
白話:
如果延遲加載實(shí)現(xiàn)的單例需要并發(fā)控制;如果初始化的時(shí)候new單例對(duì)象,本身是線程安全的,取得實(shí)例方法不需要同步。
2.【強(qiáng)制】創(chuàng)建線程或線程池時(shí)請(qǐng)指定有意義的線程名稱,方便出錯(cuò)時(shí)回溯。
正例:
- public class TimerTaskThread extends Thread {
- public TimerTaskThread() {
- super.setName("TimerTaskThread");
- ...
- }
- }
白話:
寫(xiě)代碼的時(shí)候就要想到查bug的時(shí)候要用到什么信息,然后決定如何命名、打印日志等。
3.【強(qiáng)制】線程資源必須通過(guò)線程池提供,不允許在應(yīng)用中自行顯式創(chuàng)建線程。
說(shuō)明: 使用線程池的好處是減少在創(chuàng)建和銷(xiāo)毀線程上所花的時(shí)間以及系統(tǒng)資源的開(kāi)銷(xiāo),解決資 源不足的問(wèn)題。如果不使用線程池,有可能造成系統(tǒng)創(chuàng)建大量同類(lèi)線程而導(dǎo)致消耗完內(nèi)存或者 “過(guò)度切換”的問(wèn)題。
白話:
一個(gè)是使用線程池緩存線程可以提高效率,另外線程池幫我們做了管理線程的事情,提供了優(yōu)雅關(guān)機(jī)、interrupt等待IO的線程,飽和策略等功能。
4.【強(qiáng)制】線程池不允許使用 Executors 去創(chuàng)建,而是通過(guò) ThreadPoolExecutor 的方式,這樣的處理方式讓寫(xiě)的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)。
說(shuō)明: 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。
白話:
線程池如果沒(méi)有限制最大數(shù)量,線程池?fù)伍_(kāi)的時(shí)候,由于內(nèi)存不夠或者系統(tǒng)配置的最大線程數(shù)超出,都會(huì)產(chǎn)生oom: unalbe to create native thread。
一個(gè)組件的核心參數(shù)最好要顯式的傳入,不要默認(rèn),就像你交給屬下一個(gè)任務(wù),任務(wù)的目標(biāo)、原則、時(shí)間點(diǎn)、邊界都要明確,不能模糊處理一樣,免得扯皮。
5.【強(qiáng)制】SimpleDateFormat 是線程不安全的類(lèi),一般不要定義為static變量,如果定義為
static,必須加鎖,或者使用 DateUtils 工具類(lèi)。
正例: 注意線程安全,使用 DateUtils。亦推薦如下處理:
- @Override
- protected DateFormat initialValue() {
- return new SimpleDateFormat("yyyy-MM-dd");
- }
說(shuō)明: 如果是 JDK8 的應(yīng)用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替 Simpledateformatter,官方給出的解釋: simple beautiful strong immutable thread-safe。
白話:
記住,打死你,我也不會(huì)把SimpleDateFormat共享到類(lèi)中。
【強(qiáng)制】高并發(fā)時(shí),同步調(diào)用應(yīng)該去考量鎖的性能損耗。能用無(wú)鎖數(shù)據(jù)結(jié)構(gòu),就不要用鎖; 能鎖區(qū)塊,就不要鎖整個(gè)方法體; 能用對(duì)象鎖,就不要用類(lèi)鎖。
白話:
優(yōu)先無(wú)鎖,不用鎖能解決的一定不要用鎖,即使用鎖也要控制粒度,越細(xì)越好。
7.【強(qiáng)制】對(duì)多個(gè)資源、數(shù)據(jù)庫(kù)表、對(duì)象同時(shí)加鎖時(shí),需要保持一致的加鎖順序,否則可能會(huì)造 成死鎖。
說(shuō)明: 線程一需要對(duì)表 A、B、C 依次全部加鎖后才可以進(jìn)行更新操作,那么線程二的加鎖順序也必須是 A、B、C,否則可能出現(xiàn)死鎖。
白話:
解決死鎖的方法:按順序鎖資源、超時(shí)、優(yōu)先級(jí)、死鎖檢測(cè)等。
可參考哲學(xué)家進(jìn)餐問(wèn)題學(xué)習(xí)更深入的并發(fā)機(jī)制。
8.【強(qiáng)制】并發(fā)修改同一記錄時(shí),避免更新丟失,需要加鎖。要么在應(yīng)用層加鎖,要么在緩存加 鎖,要么在數(shù)據(jù)庫(kù)層使用樂(lè)觀鎖,使用 version 作為更新依據(jù)。
說(shuō)明: 如果每次訪問(wèn)沖突概率小于 20%,推薦使用樂(lè)觀鎖,否則使用悲觀鎖。樂(lè)觀鎖的重試次 數(shù)不得小于 3 次。
白話:
狀態(tài)流轉(zhuǎn)、維護(hù)可用余額等最好直接利用數(shù)據(jù)庫(kù)的行級(jí)鎖,不需要顯式的加鎖。
9.【強(qiáng)制】多線程并行處理定時(shí)任務(wù)時(shí),Timer 運(yùn)行多個(gè) TimerTask 時(shí),只要其中之一沒(méi)有捕獲拋出的異常,其它任務(wù)便會(huì)自動(dòng)終止運(yùn)行,使用 ScheduledExecutorService 則沒(méi)有這個(gè)問(wèn)題。
白話:
線程執(zhí)行體、任務(wù)最上層等一定要抓住Throwable并進(jìn)行相應(yīng)的處理,否則會(huì)使線程終止。
10.【推薦】使用 CountDownLatch 進(jìn)行異步轉(zhuǎn)同步操作,每個(gè)線程退出前必須調(diào)用 countDown 方法,線程執(zhí)行代碼注意 catch 異常,確保 countDown 方法可以執(zhí)行,避免主線程無(wú)法執(zhí)行至 await 方法,直到超時(shí)才返回結(jié)果。
說(shuō)明: 注意,子線程拋出異常堆棧,不能在主線程 try-catch 到。
白話:
請(qǐng)?jiān)趖ry...finally語(yǔ)句里執(zhí)行countDown方法,與關(guān)閉資源類(lèi)似。
11.【推薦】避免 Random 實(shí)例被多線程使用,雖然共享該實(shí)例是線程安全的,但會(huì)因競(jìng)爭(zhēng)同一 seed 導(dǎo)致的性能下降。
說(shuō)明: Random 實(shí)例包括 java.util.Random 的實(shí)例或者 Math.random()實(shí)例。
正例: 在 JDK7 之后,可以直接使用 API ThreadLocalRandom,在 JDK7 之前,可以做到每個(gè)線程一個(gè)實(shí)例。
白話:
可以把Random放在ThreadLocal里,只在本線程中使用。
12.【推薦】在并發(fā)場(chǎng)景下,通過(guò)雙重檢查鎖(double-checked locking)實(shí)現(xiàn)延遲初始化的優(yōu) 化問(wèn)題隱患(可參考 The "Double-Checked Locking is Broken" Declaration),推薦問(wèn) 題解決方案中較為簡(jiǎn)單一種(適用于 JDK5 及以上版本),將目標(biāo)屬性聲明為 volatile 型。
反例:
- class Foo {
- private Helper helper = null;
- public Helper getHelper() {
- if (helper == null)
- synchronized(this) {
- if (helper == null)
- helper = new Helper();
- }
- return helper;
- }
- // other functions and members...
- }
白話:
網(wǎng)上對(duì)雙檢鎖有N多討論,這里很負(fù)責(zé)任的告訴大家,只要不是特別老的JDK版本(1.4以下),雙檢鎖是沒(méi)問(wèn)題的。
13.【參考】volatile 解決多線程內(nèi)存不可見(jiàn)問(wèn)題。對(duì)于一寫(xiě)多讀,是可以解決變量同步問(wèn)題, 但是如果多寫(xiě),同樣無(wú)法解決線程安全問(wèn)題。如果是 count++操作,使用如下類(lèi)實(shí)現(xiàn): AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推薦使用 LongAdder 對(duì)象,比 AtomicLong 性能更好(減少樂(lè)觀鎖的重試次數(shù))。
白話:
volatile只有內(nèi)存可見(jiàn)性語(yǔ)義,synchronized有互斥語(yǔ)義,一寫(xiě)多讀使用volatile就可以,多寫(xiě)就必須使用synchronized,fetch-mod-get也必須使用synchronized。
14.【參考】 HashMap 在容量不夠進(jìn)行 resize 時(shí)由于高并發(fā)可能出現(xiàn)死鏈,導(dǎo)致 CPU 飆升,在開(kāi)發(fā)過(guò)程中注意規(guī)避此風(fēng)險(xiǎn)。
白話:
開(kāi)發(fā)程序的時(shí)候要預(yù)估使用量,根據(jù)使用量來(lái)設(shè)置初始值。
resize需要重建hash表,嚴(yán)重影響性能,會(huì)讓程序產(chǎn)生長(zhǎng)尾的響應(yīng)時(shí)間。
15.【參考】ThreadLocal 無(wú)法解決共享對(duì)象的更新問(wèn)題,ThreadLocal 對(duì)象建議使用 static 修飾。這個(gè)變量是針對(duì)一個(gè)線程內(nèi)所有操作共有的,所以設(shè)置為靜態(tài)變量,所有此類(lèi)實(shí)例共享此靜態(tài)變量 ,也就是說(shuō)在類(lèi)第一次被使用時(shí)裝載,只分配一塊存儲(chǔ)空間,所有此類(lèi)的對(duì)象(只 要是這個(gè)線程內(nèi)定義的)都可以操控這個(gè)變量。
白話:
ThreadLocal實(shí)際上是一個(gè)從線程ID到變量的Map,每次取得ThreadLocal變量,實(shí)際上是先取得當(dāng)前線程ID,再用當(dāng)前線程ID取得關(guān)聯(lián)的變量。
ThreadLocal使用了WeakHashMap,在key被回收的時(shí)候,value也被回收了,不用擔(dān)心內(nèi)存泄露。
7 控制語(yǔ)句
1.【強(qiáng)制】在一個(gè) switch 塊內(nèi),每個(gè) case 要么通過(guò) break/return 等來(lái)終止,要么注釋說(shuō)明程序?qū)⒗^續(xù)執(zhí)行到哪一個(gè) case 為止;在一個(gè) switch 塊內(nèi),都必須包含一個(gè) default 語(yǔ)句并且放在最后,即使它什么代碼也沒(méi)有。
白話:
最好每個(gè)case都用break結(jié)束,不要組合幾個(gè)分支到一個(gè)邏輯,太不直觀。
2.【強(qiáng)制】在 if/else/for/while/do 語(yǔ)句中必須使用大括號(hào),即使只有一行代碼,避免使用 下面的形式:if (condition) statements;
白話:
這條有歧義,個(gè)人認(rèn)為有的時(shí)候就一行語(yǔ)句不加也可以。
3.【推薦】推薦盡量少用 else, if-else 的方式可以改寫(xiě)成:
- if (condition) { ...
- return obj;
- }
- // 接著寫(xiě) else 的業(yè)務(wù)邏輯代碼;
說(shuō)明: 如果非得使用if()...else if()...else...方式表達(dá)邏輯,【強(qiáng)制】請(qǐng)勿超過(guò)3層, 超過(guò)請(qǐng)使用狀態(tài)設(shè)計(jì)模式。
正例: 邏輯上超過(guò) 3 層的 if-else 代碼可以使用衛(wèi)語(yǔ)句,或者狀態(tài)模式來(lái)實(shí)現(xiàn)。
白話:
朋友說(shuō)超過(guò)三層考慮狀態(tài)設(shè)計(jì)模式也不完全正確,大概可以理解為多層的邏輯嵌套不是好的代碼風(fēng)格,需要使用對(duì)應(yīng)的重構(gòu)方法做出優(yōu)化,而每種壞味都有對(duì)應(yīng)的優(yōu)化方法和步驟,以及優(yōu)缺點(diǎn)限制條件。
寫(xiě)程序一定要遵守紅花綠葉原則,主邏輯放在主方法中,這是紅花,子邏輯封裝成小方法調(diào)用,這是綠葉,不要把不同層次的邏輯寫(xiě)在一個(gè)大方法體里,很難理解,就像綠葉把紅花擋住了,誰(shuí)還能看到。舉例說(shuō)明:
- public void handleProcess() {
- // 骨架邏輯
- validate();
- doProcess();
- declareResource();
- }
普及一下,如下類(lèi)似排比句的代碼就是衛(wèi)語(yǔ)句,以前每天都這么寫(xiě)但是還真是剛剛知道這叫衛(wèi)語(yǔ)句:)
- public double getPayAmount() {
- if (isDead()) return deadPayAmount();
- if (isSeparated()) return separatedPayAmount();&n
文章標(biāo)題:白話阿里巴巴Java開(kāi)發(fā)手冊(cè)(編程規(guī)約)
文章起源:http://www.fisionsoft.com.cn/article/dhhegcs.html


咨詢
建站咨詢
