版本號 | 版本名 | 更新日期 | 備註 |
---|---|---|---|
1.3.0 | 終極版 | 2017.09.25 | 單元測試規約,IDE代碼規約插件 |
1.3.1 | 記念版 | 2017.11.30 | 修正部分描述 |
1.4.0 | 詳盡版 | 2018.05.20 | 增長設計規約大類,共16條 |
1.5.0 | 華山版 | 2019.06.19 | 詳細更新見下面 |
本筆記主要基於華山版
(1.5.0)的總結。華山版具體更新以下:java
Java開發手冊
的限定詞阿里巴巴
switch
的NPE問題、浮點數的比較、無泛型限制、鎖的使用方式、判斷表達式、日期格式等IFNULL
的判斷、集合的toArray
、日誌處理等enum
示例、finally
的return
示例等。PDF下載地址: https://pan.baidu.com/s/1K-GZ_CzRC0igIxMgLGVtZQ
密碼:關注行無際
的微信公衆號:it_wild
,回覆java開發手冊
程序員
POJO
(Plain Ordinary Java Object): 在本手冊中,POJO專指只有setter、getter、toString的簡單類,包括DO、DTO、BO、VO等。GAV
(GroupId、ArtifactctId、Version): Maven座標,是用來惟一標識jar包。OOP
(Object Oriented Programming): 本手冊泛指類、對象的編程處理方式。ORM
(Object Relation Mapping): 對象關係映射,對象領域模型與底層數據之間的轉換,本文泛指ibatis, mybatis等框架。NPE
(java.lang.NullPointerException): 空指針異常。SOA
(Service-Oriented Architecture): 面向服務架構,它能夠根據需求經過網絡對鬆散耦合的粗粒度應用組件進行分佈式部署、組合和使用,有利於提高組件可重用性,可維護性。IDE
(Integrated Development Environment): 用於提供程序開發環境的應用程序,通常包括代碼編輯器、編譯器、調試器和圖形用戶界面等工具,本《手冊》泛指 IntelliJ IDEA 和eclipse。OOM
(Out Of Memory): 源於java.lang.OutOfMemoryError,當JVM沒有足夠的內存來爲對象分配空間而且垃圾回收器也沒法回收空間時,系統出現的嚴重情況。一方庫
:本工程內部子項目模塊依賴的庫(jar包)。二方庫
:公司內部發布到中央倉庫,可供公司內部其它應用依賴的庫(jar包)。三方庫
:公司以外的開源庫(jar包)。正則表達式
alibaba
/ youku
/ hangzhou
等UpperCamelCase
風格,但如下情形例外:DO
/ BO
/ DTO
/ VO
/ AO
/ PO
/ UID
等。如:UserDO
/ XmlService
/ TcpUdpDeal
lowerCamelCase
風格,必須聽從駝峯形式;localValue
/ getHttpMessage
/ inputUserId
MAX_STOCK_COUNT
/ CACHE_EXPIRED_TIME
Abstract
或Base
開頭;異常類命名使用Exception
結尾;測試類命名以它要測試的類的名稱開始,以Test
結尾int[] arrayDemo;
包名
統一使用小寫
,點分隔符之間有且僅有一個天然語義的英語單詞。包名統一使用單數
形式,可是類名
若是有複數含義,類名可使用複數形式
。包名com.alibaba.ai.util
,類名爲MessageUtils
(此規則參考spring
的框架結構)AtomicReferenceFieldUpdater
startTime
/ workQueue
/ nameList
/ TERMINATED_THREAD_COUNT
設計模式
,在命名時需體現出具體模式(將設計模式體如今名字中,有利於閱讀者快速理解架構設計理念)。如: class OrderFactory
/ class LoginProxy
/ class ResourceObserver
public
也不要加),保持代碼的簡潔性,並加上有效的Javadoc
註釋。儘可能不要在接口裏定義變量,若是必定要定義變量,確定是與接口方法相關,而且是整個應用的基礎常量。接口方法簽名void commit();
,接口基礎常量String COMPANY = "alibaba";
Service
和DAO
類,基於SOA
的理念,暴露出來的服務必定是接口,內部的實現類用Impl
的後綴與接口區別。如CacheServiceImpl
實現CacheService
接口形容能力
的接口
名稱,取對應的形容詞爲接口名(一般是–able
的形容詞)如 AbstractTranslator
實現Translatable
接口Enum
後綴,枚舉成員名稱須要全大寫
,單詞間用下劃線
隔開。(說明:枚舉其實就是特殊的類,域成員均爲常量,且構造方法被默認強制是私有。)枚舉名字爲ProcessStatusEnum
的成員名稱:SUCCESS
/ UNKNOWN_REASON
各層命名規約:spring
A) Service/DAO 層方法命名規約
- 獲取單個對象的方法用
get
作前綴。- 獲取多個對象的方法用
list
作前綴,複數形式結尾如:listObjects
。- 獲取統計值的方法用
count
作前綴。- 插入的方法用
save
/insert
作前綴。- 刪除的方法用
remove
/delete
作前綴。- 修改的方法用
update
作前綴。
B) 領域模型命名規約數據庫
- 數據對象:
xxxDO
,xxx即爲數據表名。- 數據傳輸對象:
xxxDTO
,xxx爲業務領域相關的名稱。- 展現對象:
xxxVO
,xxx通常爲網頁名稱。- POJO是DO/DTO/BO/VO的統稱,禁止命名成xxxPOJO。
反例:編程
AbstractClass
「縮寫」命名成condition
「縮寫」命名成POJO類中布爾類型
變量都不要加is
前綴,不然部分框架解析會引發序列化錯誤
。設計模式
定義爲基本數據類型
Boolean isDeleted
的屬性,它的方法也是isDeleted()
,RPC框架在反向解析的時候,「誤覺得」對應的屬性名稱是deleted
,致使屬性獲取不到,進而拋出異常。數組
"Id#taobao_"
+ tradeId;long
或者Long
賦值時,數值後使用大寫的L
,不能是小寫的l
,小寫容易跟數字1混淆,形成誤解。CacheConsts
下;系統配置相關常量放在類ConfigConsts
下。enum
類型來定義。tab
字符。int second = (int)first + 2;
不一樣邏輯、不一樣語義、不一樣業務的代碼之間插入一個空行
分隔開來以提高可讀性(說明:任何情形,沒有必要插入多個空行進行隔開
。)緩存
對象引用
訪問此類的靜態變量
或靜態方法
,無謂增長編譯器解析成本,直接用類名來訪問便可@Override
註解@Deprecated
註解,並清晰地說明採用的新接口或者新服務是麼。equals
方法容易拋空指針異常,應使用常量或肯定有值的對象來調用equals,"test".equals(object);
【推薦使用 java.util.Objects#equals(JDK7 引入的工具類】equals
方法比較。【在-128至127這個區間以外的全部數據,都會在堆上產生,並不會複用已有對象,這是一個大坑,推薦使用equals方法進行判斷】DO
類時,屬性類型要與數據庫字段類型相匹配。數據庫字段的bigint
必須與類屬性的Long
類型相對應。BigDecimal(double)
的方式把double
值轉化爲BigDecimal
對象(在精確計算或值比較的場景中可能會致使業務邏輯異常)。BigDecimal g = new BigDecimal(0.1f);
實際的存儲值爲:0.10000000149
。正例:優先推薦入參爲String
的構造方法,或使用BigDecimal
的valueOf
方法,此方法內部其實執行了Double
的toString
,而Double的toString
按double
的實際能表達的精度對尾數進行了截斷。BigDecimal recommend1 = new BigDecimal("0.1"); BigDecimal recommend2 = BigDecimal.valueOf(0.1);
1)【強制】全部的POJO類屬性必須使用包裝數據類型。2)【強制】RPC方法的返回值和參數必須使用包裝數據類型。3) 【推薦】全部的局部變量使用基本數據類型。
【說明:POJO類屬性沒有初值是提醒使用者在須要使用時,必須本身顯式地進行賦值,任何NPE問題,或者入庫檢查,都由使用者來保證。正例:數據庫的查詢結果多是null,由於自動拆箱,用基本數據類型接收有NPE風險。反例:好比顯示成交總額漲跌狀況,即正負x%,x爲基本數據類型,調用的RPC服務,調用不成功時,返回的是默認值,頁面顯示爲0%,這是不合理的,應該顯示成中劃線。因此包裝數據類型的null值,可以表示額外的信息,如:遠程調用失敗,異常退出。
】POJO
類時,不要設定任何屬性默認值
。【反例:POJO 類的 createTime 默認值爲 new Date(),可是這個屬性在數據提取時並無置入具體值,在更新其它字段時又附帶更新了此字段,致使建立時間被修改爲當前時間。】serialVersionUID
字段,避免反序列失敗;若是徹底不兼容升級,避免反序列化混亂,那麼請修改serialVersionUID
值。(說明:注意serialVersionUID不一致會拋出序列化運行時異常。)init
方法中。POJO
類必須寫toString
方法。使用IDE中的工具:source> generate toString時,若是繼承了另外一個POJO類,注意在前面加一下super.toString
。【說明:在方法執行拋出異常時,能夠直接調用 POJO 的 toString()
方法打印其屬性值,便於排查問題】POJO
類中,同時存在對應屬性xxx的isXxx()
和getXxx()
方法。【說明:框架在調用屬性 xxx 的提取方法時,並不能肯定哪一個方法必定是被優先調用到】split
方法獲得的數組時,需作最後一個分隔符後有無內容的檢查,不然會有拋IndexOutOfBoundsException
的風險getter/setter
方法中,不要增長業務邏輯,增長排查問題的難度StringBuilder
的append
方法進行擴展final
能夠聲明類(不容許被繼承的類,如String
類)、成員變量(不容許修改引用的域對象)、方法、以及本地變量(不容許運行過程當中從新賦值的局部變量),避免上下文重複使用一個變量,使用final能夠強制從新定義一個變量,方便更好地進行重構Object
的clone
方法來拷貝對象,對象clone
方法默認是淺拷貝,若想實現深拷貝需覆寫clone
方法實現域對象的深度遍歷式拷貝。1)若是不容許外部直接經過new來建立對象,那麼構造方法必須是private。2)工具類不容許有public或default構造方法。3)類非static 成員變量而且與子類共享,必須是protected。 4)類非static成員變量而且僅在本類使用,必須是private。5)類static成員變量若是僅在本類使用,必須是private。 6)如果static成員變量,考慮是否爲final。7)類成員方法只供類內部調用,必須是 private。8)類成員方法只對繼承類公開,那麼限制爲protected。
【說明:任何類、方法、參數、變量,嚴控訪問範圍。過於寬泛的訪問範圍,不利於模塊解耦】浮點數
之間的等值判斷,基本數據類型不能用==來比較,包裝數據類型不能用equals 來判斷。【浮點數採用「尾數+階碼」的編碼方式,相似於科學計數法的「有效數字+指數」的表示方式】// 反例 float a = 1.0f - 0.9f; float b = 0.9f - 0.8f; if (a == b) { // 預期進入此代碼快,執行其它業務邏輯 // 但事實上 a==b 的結果爲 false } Float x = Float.valueOf(a); Float y = Float.valueOf(b); if (x.equals(y)) { // 預期進入此代碼快,執行其它業務邏輯 // 但事實上 equals 的結果爲 false } // 正例 // (1)指定一個偏差範圍,兩個浮點數的差值在此範圍以內,則認爲是相等的 float a = 1.0f - 0.9f; float b = 0.9f - 0.8f; float diff = 1e-6f; if (Math.abs(a - b) < diff) { System.out.println("true"); } // (2)使用BigDecimal來定義值,再進行浮點數的運算操做 // BigDecimal構造的時候注意事項 見上文 BigDecimal a = new BigDecimal("1.0"); BigDecimal b = new BigDecimal("0.9"); BigDecimal c = new BigDecimal("0.8"); BigDecimal x = a.subtract(b); BigDecimal y = b.subtract(c); if (x.equals(y)) { System.out.println("true"); }
hashCode
和equals
的處理,遵循以下規則:1)只要覆寫equals
,就必須覆寫hashCode
。2)由於Set
存儲的是不重複的對象,依據hashCode
和equals
進行判斷,因此Set
存儲的對象必須覆寫這兩個方法。3)若是自定義對象做爲Map
的鍵,那麼必須覆寫hashCode
和equals
。【說明:String
已覆寫hashCode
和equals
方法,因此咱們能夠愉快地使用String
對象做爲key
來使用】ArrayList
的subList
結果不可強轉成ArrayList
,不然會拋出ClassCastException
異常,即java.util.RandomAccessSubList cannot be cast to java.util.ArrayList【說明:subList
返回的是ArrayList
的內部類SubList
,並非ArrayList
而是ArrayList
的一個視圖,對於SubList
子列表的全部操做最終會反映到原列表上】Map
的方法keySet()/values()/entrySet()
返回集合對象時,不能夠對其進行添加元素操做,不然會拋出UnsupportedOperationException
異常Collections
類返回的對象,如:emptyList()/singletonList()
等都是immutable list,不可對其進行添加或者刪除元素的操做【反例:若是查詢無結果,返回 Collections.emptyList()
空集合對象,調用方一旦進行了添加元素的操做,就會觸發UnsupportedOperationException
異常。】subList
場景中,高度注意對原集合元素的增長或刪除,均會致使子列表的遍歷、增長、刪除產生ConcurrentModificationException
異常toArray(T[] array)
,傳入的是類型徹底一致、長度爲0的空數組【反例:直接使用toArray無參方法存在問題,此方法返回值只能是Object[]類,若強轉其它類型數組將出現ClassCastException錯誤。】// 正例 List<String> list = new ArrayList<>(2); list.add("行無際"); list.add("itwild"); String[] array = list.toArray(new String[0]); /* 說明: 使用toArray帶參方法,數組空間大小的length: 1)等於0,動態建立與size相同的數組,性能最好 2)大於0但小於size,從新建立大小等於size的數組,增長GC負擔 3)等於size,在高併發狀況下,數組建立完成以後,size正在變大的狀況下,負面影響與上相同 4)大於size,空間浪費,且在size處插入null值,存在NPE隱患 */
Collection
接口任何實現類的addAll()
方法時,都要對輸入的集合參數進行NPE判斷 【說明:在ArrayList#addAll
方法的第一行代碼即Object[] a = c.toArray();
其中c爲輸入集合參數,若是爲null,則直接拋出異常。】Arrays.asList()
把數組轉換成集合時,不能使用其修改集合相關的方法,它的add/remove/clear
方法會拋出UnsupportedOperationException
異常【說明:asList
的返回對象是一個Arrays
內部類,並無實現集合的修改方法。Arrays.asList
體現的是適配器模式,只是轉換接口,後臺的數據還是數組。】String[] str = new String[] { "it", "wild" }; List list = Arrays.asList(str); // 第一種狀況:list.add("itwild"); 運行時異常 // 第二種狀況:str[0] = "changed1"; 也會隨之修改 // 反之亦然 list.set(0, "changed2");
泛型通配符<? extends T>
來接收返回的數據,此寫法的泛型集合不能使用add方法,而<? super T>
不能使用get方法,做爲接口調用賦值時易出錯。【說明:擴展說一下PECS
(Producer Extends Consumer Super)原則:第1、頻繁往外讀取內容的,適合用<? extends T>
。第2、常常往裏插入的,適合用<? super T>
】安全
這個地方我以爲有必要簡單解釋一下(
行無際
本人的我的理解哈,有不對的地方歡迎指出),上面的說法可能有點官方或者難懂。其實咱們一直也是這麼幹的,不過沒注意而已。舉個最簡單的例子,用泛型
的時候,若是你遍歷
(read
)一個List,你是否是但願List裏面裝的越具體越好啊,你但願裏面裝的是Object
嗎,若是裏面裝的是Object
那麼你想一想你會有多痛苦,每一個對象都用instanceof
判斷一下再類型強轉
,因此這個方法的參數List主要用於遍歷
(read
)的時候,大多數狀況你可能會要求裏面的元素最大是T
類型,即用<? extends T>
限制一下。再看你往List裏面插入
(write
)數據又會怎麼樣,爲了靈活性和可擴展性,你立刻可能就要說我固然但願List裏面裝的是Object
了,這樣我什麼類型的對象都能往List裏面寫啊,這樣設計出來的接口的靈活性和可擴展性才強啊,若是裏面裝的類型太靠下(假定繼承層次從上往下
,父類在上,子孫類在下),那麼位於上級的不少類型的數據你就沒法寫入了,這個時候用<? super T>
來限制一下最小是T
類型。下面咱們來看Collections.copy()
這個例子。
// 這裏就要求dest的List裏面的元素類型 不能在src的List元素類型 之下 // 若是dest的List元素類型位於src的List元素類型之下,就會出現寫不進dest public static <T> void copy(List<? super T> dest, List<? extends T> src) { //....省略具體的copy代碼 } // 下面再看我寫的測試代碼就更容易理解了 static class Animal {} static class Dog extends Animal {} static class BlackDog extends Dog {} @Test public void test() throws Exception { List<Dog> dogList = new ArrayList<>(2); dogList.add(new BlackDog()); dogList.add(new BlackDog()); List<Animal> animalList = new ArrayList<>(2); animalList.add(new Animal()); animalList.add(new Animal()); // 錯誤,沒法編譯經過 Collections.copy(dogList, animalList); // 正確 Collections.copy(animalList, dogList); // Collections.copy()的泛型參數就起做到了很好的限制做用 // 編譯期就能發現類型不對 }
instanceof
判斷,避免拋出ClassCastException
異常。// 反例 List<String> generics = null; List notGenerics = new ArrayList(10); notGenerics.add(new Object()); notGenerics.add(new Integer(1)); generics = notGenerics; // 此處拋出 ClassCastException 異常 String string = generics.get(0);
foreach
循環裏進行元素的remove/add
操做。remove
元素請使用Iterator
方式,若是併發操做,須要對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); } }
Comparator
實現類要知足以下三個條件,否則Arrays.sort
,Collections.sort
會拋IllegalArgumentException
異常【說明:三個條件以下 1)x,y 的比較結果和 y,x 的比較結果相反。2)x>y,y>z,則x>z。 3) x=y,則x,z比較結果和y,z 比較結果相同。】// 反例:下例中沒有處理相等的狀況 new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o1.getId() > o2.getId() ? 1 : -1; } };
// 正例 // diamond 方式,即<> HashMap<String, String> userCache = new HashMap<>(16); // 全省略方式 ArrayList<User> users = new ArrayList(10);
集合初始化時,指定集合初始值大小【說明:HashMap
使用HashMap(int initialCapacity)
初始化。】
正例:
initialCapacity
= (須要存儲的元素個數 / 負載因子) + 1。注意負載因子(即loader factor
)默認爲0.75,若是暫時沒法肯定初始值大小,請設置爲16(即默認值)。
反例:
HashMap
須要放置1024個元素,因爲沒有設置容量初始大小,隨着元素不斷增長,容量7次被迫擴大,resize
須要重建hash
表,嚴重影響性能。
使用entrySet
遍歷Map
類集合KV,而不是keySet
方式進行遍歷。【說明:keySet
實際上是遍歷了2次,一次是轉爲Iterator
對象,另外一次是從hashMap
中取出 key
所對應的value
。而entrySet
只是遍歷了一次就把key
和value
都放到了entry中,效率更高。若是是JDK8,使用Map.forEach
方法。】
正例:
values()
返回的是V值集合,是一個list集合對象;keySet()
返回的是K值集合,是一個Set集合對象;entrySet()
返回的是K-V
值組合集合。
高度注意Map
類集合K/V
能不能存儲null
值的狀況,以下表格:
集合類 | Key | Value | Super | 說明 |
---|---|---|---|---|
Hashtable | 不容許爲null | 不容許爲null | Dictionary | 線程安全 |
ConcurrentHashMap | 不容許爲null | 不容許爲null | AbstractMap | 鎖分段技術(JDK8:CAS) |
TreeMap | 不容許爲null | 容許爲null | AbstractMap | 線程不安全 |
HashMap | 容許爲null | 容許爲null | AbstractMap | 線程不安全 |
反例:因爲
HashMap
的干擾,不少人認爲ConcurrentHashMap
是能夠置入null
值,而事實上,存儲null
值時會拋出NPE
異常。
sort
)和穩定性(order
),避免集合的無序性(unsort
)和不穩定性(unorder
)帶來的負面影響。【說明:有序性是指遍歷的結果是按某種比較規則依次排列的。穩定性指集合每次遍歷的元素次序是必定的。如:ArrayList
是 order/unsort
;HashMap
是unorder/unsort
;TreeSet
是order/sort
。】Set
元素惟一的特性,能夠快速對一個集合進行去重操做,避免使用List
的contains
方法進行遍歷、對比、去重操做// 正例:自定義線程工廠,而且根據外部特徵進行分組,好比機房信息 public class UserThreadFactory implements ThreadFactory { private final String namePrefix; private final AtomicInteger nextId = new AtomicInteger(1); // 定義線程組名稱,在 jstack 問題排查時,很是有幫助 UserThreadFactory(String whatFeaturOfGroup) { namePrefix = "From UserThreadFactory's " + whatFeaturOfGroup + "-Worker-"; } @Override public Thread newThread(Runnable task) { String name = namePrefix + nextId.getAndIncrement(); Thread thread = new Thread(null, task, name, 0, false); System.out.println(thread.getName()); return thread; } }
線程池不容許使用Executors
去建立,而是經過ThreadPoolExecutor
的方式,這樣的處理方式讓寫的同窗更加明確線程池的運行規則,規避資源耗盡的風險
說明:
Executors
返回的線程池對象的弊端以下:
1)
FixedThreadPool
和SingleThreadPool
:
容許的請求隊列長度爲Integer.MAX_VALUE
,可能會堆積大量的請求,從而致使OOM
。
2)
CachedThreadPool
:
容許的建立線程數量爲Integer.MAX_VALUE
,可能會建立大量的線程,從而致使OOM
。
SimpleDateFormat
是線程不安全的類,通常不要定義爲static
變量,若是定義爲static
,必須加鎖。【說明:若是是JDK8的應用,可使用Instant
代替Date
,LocalDateTime
代替Calendar
,DateTimeFormatter
代替SimpleDateFormat
,官方給出的解釋:simple beautiful strong immutable thread-safe
。】// 正例:注意線程安全。亦推薦以下處理 private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } };
ThreadLocal
變量,尤爲在線程池場景下,線程常常會被複用,若是不清理自定義的ThreadLocal
變量,可能會影響後續業務邏輯和形成內存泄露等問題。儘可能使用try-finally
塊進行回收// 正例 objectThreadLocal.set(userInfo); try { // ... } finally { objectThreadLocal.remove(); }
RPC
方法。】在使用阻塞等待獲取鎖的方式中,必須在try
代碼塊以外,而且在加鎖方法與try
代碼塊之間沒有任何可能拋出異常的方法調用,避免加鎖成功後,在finally
中沒法解鎖
說明一:若是在
lock
方法與try
代碼塊之間的方法調用拋出異常,那麼沒法解鎖,形成其它線程沒法成功獲取鎖。
說明二:若是
lock
方法在try
代碼塊以內,可能因爲其它方法拋出異常,致使在finally
代碼塊中,unlock
對未加鎖的對象解鎖,它會調用AQS
的tryRelease
方法(取決於具體實現類),拋出IllegalMonitorStateException
異常。
說明三:在
Lock
對象的lock
方法實現中可能拋出unchecked
異常,產生的後果與說明二相同
// 正例 Lock lock = new XxxLock(); // ... lock.lock(); try { doSomething(); doOthers(); } finally { lock.unlock(); } // 反例 Lock lock = new XxxLock(); // ... try { // 若是此處拋出異常,則直接執行 finally 代碼塊 doSomething(); // 不管加鎖是否成功,finally 代碼塊都會執行 lock.lock(); doOthers(); } finally { lock.unlock(); }
// 正例 Lock lock = new XxxLock(); // ... boolean isLocked = lock.tryLock(); if (isLocked) { try { doSomething(); doOthers(); } finally { lock.unlock(); } }
version
做爲更新依據【說明:若是每次訪問衝突機率小於20%,推薦使用樂觀鎖,不然使用悲觀鎖。樂觀鎖的重試次數不得小於3次。】Timer
運行多個TimeTask
時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,若是在處理定時任務時使用ScheduledExecutorService
則沒有這個問題CountDownLatch
進行異步轉同步操做,每一個線程退出前必須調用countDown
方法,線程執行代碼注意catch異常,確保countDown
方法被執行到,避免主線程沒法執行至await
方法,直到超時才返回結果【說明:注意,子線程拋出異常堆棧,不能在主線程try-catch
到。】Random
實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一seed
致使的性能降低【說明:Random
實例包括java.util.Random
的實例或者Math.random()
的方式。正例:在JDK7以後,能夠直接使用APIThreadLocalRandom
,而在JDK7以前,須要編碼保證每一個線程持有一個實例】(double-checked locking)
實現延遲初始化的優化問題隱患(可參考 The "Double-Checked Locking is Broken" Declaration),推薦解決方案中較爲簡單一種(適用於JDK5及以上版本),將目標屬性聲明爲volatile
型。// 注意 這裏的代碼並不是出自官方的《java開發手冊》 // 參考 https://blog.csdn.net/lovelion/article/details/7420886 public class LazySingleton { // volatile除了保證內容可見性還有防止指令重排序 // 對象的建立其實是三條指令: // 一、分配內存地址 二、內存地址初始化 三、返回內存地址句柄 // 其中二、3之間可能發生指令重排序 // 重排序可能致使線程A建立對象先執行一、3兩步, // 結果線程B進來判斷句柄已經不爲空,直接返回給上層方法 // 此時對象尚未正確初始化內存,致使上層方法發生嚴重錯誤 private volatile static LazySingleton instance = null; private LazySingleton() { } public static LazySingleton getInstance() { // 第一重判斷 if (instance == null) { synchronized (LazySingleton.class) { // 第二重判斷 if (instance == null) { // 建立單例實例 instance = new LazySingleton(); } } } return instance; } } // 既然這裏提到 單例懶加載,還有這樣寫的 // 參考 https://blog.csdn.net/lovelion/article/details/7420888 class Singleton { private Singleton() { } private static class HolderClass { // 由Java虛擬機來保證其線程安全性,確保該成員變量只能初始化一次 final static Singleton instance = new Singleton(); } public static Singleton getInstance() { return HolderClass.instance; } }
volatile
解決多線程內存不可見問題。對於一寫多讀,是能夠解決變量同步問題,可是若是多寫,一樣沒法解決線程安全問題。【說明:若是是count++
操做,使用以下類實現:AtomicInteger count = new AtomicInteger();
count.addAndGet(1);
若是是JDK8,推薦使用LongAdder
對象,比AtomicLong
性能更好(減小樂觀鎖的重試次數)。】HashMap
在容量不夠進行resize
時因爲高併發可能出現死鏈,致使CPU飆升,在開發過程當中可使用其它數據結構或加鎖來規避此風險ThreadLocal
對象使用static
修飾,ThreadLocal
沒法解決共享對象的更新問題【說明:這個變量是針對一個線程內全部操做共享的,因此設置爲靜態變量,全部此類實例共享此靜態變量,也就是說在類第一次被使用時裝載,只分配一塊存儲空間,全部此類的對象(只要是這個線程內定義的)均可以操控這個變量】switch
括號內的變量類型爲String
而且此變量爲外部參數時,必須先進行null
判斷。public class SwitchString { public static void main(String[] args) { // 這裏會拋異常 java.lang.NullPointerException method(null); } public static void method(String param) { switch (param) { // 確定不是進入這裏 case "sth": System.out.println("it's sth"); break; // 也不是進入這裏 case "null": System.out.println("it's null"); break; // 也不是進入這裏 default: System.out.println("default"); } } }
if/else/for/while/do
語句中必須使用大括號。【說明:即便只有一行代碼,避免採用單行的編碼方式:if (condition) statements;
在高併發場景
中,避免使用」等於」判斷做爲中斷或退出的條件。【說明:若是併發控制沒有處理好,容易產生等值判斷被「擊穿」的狀況,使用大於或小於的區間判斷條件來代替。】
反例:判斷剩餘獎品數量等於 0 時,終止發放獎品,但由於併發處理錯誤致使獎品數量瞬間變成了負數,這樣的話,活動沒法終止。
if-else
方式,這種方式能夠改寫成下面代碼:【說明:若是非使用if()...else if()...else...
方式表達邏輯,避免後續代碼維護困難,請勿超過3
層】if (condition) { ... return obj; } // 接着寫 else 的業務邏輯代碼;
超過3層的
if-else
的邏輯判斷代碼可使用衛語句
、策略模式
、狀態模式
等來實現。其中衛語句即代碼邏輯先考慮失敗、異常、中斷、退出等直接返回的狀況,以方法多個出口的方式,解決代碼中判斷分支嵌套的問題,這是逆向思惟的體現。
// 示例代碼 public void findBoyfriend(Man man) { if (man.isUgly()) { System.out.println("本姑娘是外貌協會的資深會員"); return; } if (man.isPoor()) { System.out.println("貧賤夫妻百事哀"); return; } if (man.isBadTemper()) { System.out.println("銀河有多遠,你就給我滾多遠"); return; } System.out.println("能夠先交往一段時間看看"); }
getXxx/isXxx
)等外,不要在條件判斷中執行其它複雜的語句,將複雜邏輯判斷的結果賦值給一個有意義的布爾變量名,以提升可讀性。【說明:不少if
語句內的邏輯表達式至關複雜,與、或、取反混合運算,甚至各類方法縱深調用,理解成本很是高。若是賦值一個很是好理解的布爾變量名字,則是件使人爽心悅目的事情。】// 正例 // 僞代碼以下 final boolean existed = (file.open(fileName, "w") != null) && (...) || (...); if (existed) { ... } // 反例 // 哈哈,這好像是ReentrantLock裏面有相似風格的代碼 // 連Doug Lea的代碼都拿來當作反面教材啊 // 早前就聽別人說過「編程不識Doug Lea,寫盡Java也枉然!!!」 public final void acquire(long arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) { selfInterrupt(); } }
// 反例 public Lock getLock(boolean fair) { // 算術表達式中出現賦值操做,容易忽略 count 值已經被改變 threshold = (count = Integer.MAX_VALUE) - 1; // 條件表達式中出現賦值操做,容易誤認爲是 sync==fair return (sync = fair) ? new FairSync() : new NonfairSync(); }
try-catch
操做(這個try-catch
是否能夠移至循環體外)if (x < 628)
來表達x小於628。反例:使用 if (!(x >= 628))來表達x小於628。】下列情形,須要進行參數校驗
1) 調用頻次低的方法。2)執行時間開銷很大的方法。此情形中,參數校驗時間幾乎能夠忽略不計,但若是由於參數錯誤致使中間執行回退,或者錯誤,那得不償失。3)須要極高穩定性和可用性的方法。4)對外提供的開放接口,無論是
RPC/API/HTTP
接口。5)敏感權限入口。
下列情形,不須要進行參數校驗:
1)極有可能被循環調用的方法。但在方法說明裏必須註明外部參數檢查要求。 2)底層調用頻度比較高的方法。畢竟是像純淨水過濾的最後一道,參數錯誤不太可能到底層纔會暴露問題。通常
DAO
層與Service
層都在同一個應用中,部署在同一臺服務器中,因此DAO
的參數校驗,能夠省略。3)被聲明成private
只會被本身代碼所調用的方法,若是可以肯定調用方法的代碼傳入參數已經作過檢查或者確定不會有問題,此時能夠不校驗參數。
Javadoc
規範,使用/**內容*/
格式,不得使用// xxx
方式。【說明:在IDE編輯窗口中,Javadoc
方式會提示相關注釋,生成 Javadoc
能夠正確輸出相應註釋;在IDE中,工程調用方法時,不進入方法便可懸浮提示方法、參數、返回值的意義,提升閱讀效率。】/* */
註釋,注意與代碼對齊枚舉類型
字段必需要有註釋,說明每一個數據項的用途特殊註釋標記,請註明標記人與標記時間。注意及時處理這些標記,經過標記掃描,常常清理此類標記。線上故障有時候就是來源於這些標記處的代碼。
1)待辦事宜(
TODO
):(標記人,標記時間,[預計處理時間])表示須要實現,但目前還未實現的功能。這其實是一個Javadoc
的標籤,目前的Javadoc
還沒
有實現,但已經被普遍使用。只能應用於類,接口和方法(由於它是一個Javadoc標籤)。2)錯誤,不能工做(FIXME
):(標記人,標記時間,[預計處理時間])
在註釋中用FIXME
標記某代碼是錯誤的,並且不能工做,須要及時糾正的狀況。
Pattern pattern = Pattern.compile(「規則」);
】Math.random()
這個方法返回是double
類型,注意取值的範圍0≤x<1
(可以取到零值,注意除零異常),若是想獲取整數類型的隨機數,不要將x放大10的若干倍而後取整,直接使用Random
對象的nextInt
或者nextLong
方法。System.currentTimeMillis();
而不是new Date().getTime();
【說明:若是想獲取更加精確的納秒級時間值,使用System.nanoTime()
的方式。在JDK8中,針對統計時間等場景,推薦使用Instant
類。】pattern
中表示年份統一使用小寫的y
。【說明:日期格式化時,yyyy
表示當天所在的年,而大寫的YYYY
表明是 week in which year(JDK7以後引入的概念),意思是當天所在的周屬於的年份,一週從週日開始,週六結束,只要本週跨年,返回的YYYY
就是下一年。另外須要注意:表示月份是大寫的M,表示分鐘則是小寫的m,24小時制的是大寫的H,12小時制的則是小寫的h 。正例:new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
】///
)來講明註釋掉代碼的理由】