五.集合處理
5-1.關於 hashCode 和 equals 的處理, 遵循以下規則:程序員
1) 只要重寫equals, 就必須重寫hashCode正則表達式
2) 由於Set存儲的是不重複的對象, 依據 hashCode 和 equals 進行判斷. 因此 Set 存儲的對象必須重寫這兩個方法.數據庫
3) 若是自定義對象做爲 Map 的鍵, 那麼必須重寫 hashCode 和 equals.數組
5-2.ArrayList 的 subList 結果不能夠強轉成 ArrayList, 不然會拋出ClassCastException.安全
subList 返回的是ArrayList的內部類SubList, 並非ArrayList 而是ArrayList的一個視圖,對於SubList子列表的全部操做會最終會反映到原列表上服務器
5-3.在 subList 場景中, 高度注意對原集合元素的增長或刪除, 均會致使子列表的遍歷,增長,刪除產生 ConcurrentModificationException 異常類名使用Exception數據結構
5-4.使用集合轉數組的方法, 必須使用集合的 toArray(T[] array), 傳入的是類型徹底同樣的數組,大小就是 list.size()多線程
//最好將方法如殘數組大小定義與集合元素個數一致 List<String> list = new ArrayList<String>(2); list.add("guan"); list.add("bao"); String[] array = new String[list.size()]; array = list.toArray(array);
5-5.使用工具類 Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方法,它的 add/remove/clear 方法會拋出 UnsupportedOperationException 異常。併發
說明:asList 的返回對象是一個 Arrays 內部類,並無實現集合的修改方法。Arrays.asList 體現的是適配器模式,只是轉換接口,後臺的數據還是數組。dom
String[] str = new String[] { "you", "wu" }; List list = Arrays.asList(str); //第一種狀況:list.add("yangguanbao"); 運行時異常。 //第二種狀況:str[0] = "gujin"; 那麼 list.get(0)也會隨之修改。
5-6.泛型通配符<? extends T>來接收返回的數據,此寫法的泛型集合不能使用 add 方法,而<? super T>不能使用 get 方法,做爲接口調用賦值時易出錯。
說明:擴展說一下 PECS(Producer Extends Consumer Super)原則:
第1、頻繁往外讀取內容的,適合用<? extends T>。
第2、常常往裏插入的,適合用<? super T>。
5-7.不要在 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(); } }
5-8.在JDK7版本及以上, 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; } };
推薦:
5-9.集合泛型定義時, 在JDK7及以上, 使用 diamond 語法或全省略
說明: 菱形泛型,即 diamond,直接使用<>來指代前邊已經指定的類型。
正例: // <> diamond 方式 HashMap<String, String> userCache = new HashMap<>(16); // 全省略方式 ArrayList<User> users = new ArrayList(10);
5-10.集合初始化時,指定集合初始值大小.
說明:HashMap 使用 HashMap(int initialCapacity) 初始化。
正例:initialCapacity = (須要存儲的元素個數 / 負載因子) + 1。注意負載因子(即 loader factor)默認爲 0.75,若是暫時沒法肯定初始值大小,請設置爲 16(即默認值)。
反例:HashMap 須要放置 1024 個元素,因爲沒有設置容量初始大小,隨着元素不斷增長,容量 7 次被迫擴大,resize 須要重建 hash 表,嚴重影響性能。
5-11.使用 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 值組合集合。
5-12.高度注意 Map 類集合 K/V 能不能存儲 null 值的狀況,以下表格:
5-13.合理利用好集合的有序性(sort)和穩定性(order),避免集合的無序性(unsort)和不穩定性(unorder)帶來的負面影響。
說明:有序性是指遍歷的結果是按某種比較規則依次排列的。穩定性指集合每次遍歷的元素次序是必定的。
如:ArrayList 是 order/unsort;HashMap 是 unorder/unsort;TreeSet 是order/sort。
5-14.利用 Set 元素惟一的特性,能夠快速對一個集合進行去重操做,避免使用 List 的 contains 方法進行遍歷、對比、去重操做。
List<String> list = new ArrayList<>(); list.add(..); ... Set<String> set = new HashSet<>(list);
六.併發處理
6-1.獲取單例對象須要保證線程安全,其中的方法也要保證線程安全.
說明:資源驅動類、工具類、單例工廠類都須要注意。
6-2.建立線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。
6-3.線程資源必須經過線程池提供,不容許在應用中自行顯式建立線程。
說明:使用線程池的好處是減小在建立和銷燬線程上所消耗的時間以及系統資源的開銷,解決資源不足的問題。若是不使用線程池,有可能形成系統建立大量同類線程而致使消耗完內存或者「過分切換」的問題。
6-4.線程池不容許使用 Executors 去建立,而是經過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同窗更加明確線程池的運行規則,規避資源耗盡的風險。
說明:Executors 返回的線程池對象的弊端以下:
1)FixedThreadPool 和 SingleThreadPool:
容許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而致使 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
容許的建立線程數量爲 Integer.MAX_VALUE,可能會建立大量的線程,從而致使 OOM。
6-5.SimpleDateFormat 是線程不安全的類,通常不要定義爲 static 變量,若是定義爲static,必須加鎖,或者使用 DateUtils 工具類。
//正例:注意線程安全,使用 DateUtils。亦推薦以下處理: private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } };
說明:若是是 JDK8 的應用,可使用 Instant 代替 Date,LocalDateTime 代替 Calendar,
DateTimeFormatter 代替 SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable thread-safe。
6-6.高併發時,同步調用應該去考量鎖的性能損耗。能用無鎖數據結構,就不要用鎖;能鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。
說明:儘量使加鎖的代碼塊工做量儘量的小,避免在鎖代碼塊中調用 RPC 方法。
6-7~6-15 等接觸了多線程在看書吧.
七.控制語句
7-1.在一個 switch 塊內,每一個 case 要麼經過 break/return 等來終止,要麼註釋說明程序將繼續執行到哪個 case 爲止;在一個 switch 塊內,都必須包含一個 default 語句而且放在最後,即便空代碼。
7-2.在 if/else/for/while/do 語句中必須使用大括號。即便只有一行代碼,避免採用單行的編碼方式:if (condition) statements;
7-3.在高併發場景中,避免使用」等於」判斷做爲中斷或退出的條件。
說明:若是併發控制沒有處理好,容易產生等值判斷被「擊穿」的狀況,使用大於或小於的區間判斷條件來代替。
反例:判斷剩餘獎品數量等於 0 時,終止發放獎品,但由於併發處理錯誤致使獎品數量瞬間變成了負數,這樣的話,活動沒法終止。
推薦:
7-4.表達異常的分支時,少用 if-else 方式,這種方式能夠改寫成:
if (condition) {
...
return obj;
}
// 接着寫 else 的業務邏輯代碼;說明: 若是非得使用, 請勿超過 3 層。超過 3 層的 if-else 的邏輯判斷代碼可使用 衛語句、策略模式、狀態模式 等來實現
7-5.除經常使用方法(如 getXxx/isXxx)等外,不要在條件判斷中執行其它複雜的語句,將複雜邏輯判斷的結果賦值給一個有意義的布爾變量名,以提升可讀性。
7-6.循環體中的語句要考量性能,如下操做盡可能移至循環體外處理,如定義對象、變量、獲取數據庫鏈接,進行沒必要要的 try-catch 操做(這個 try-catch 是否能夠移至循環體外)。
7-7.避免採用取反邏輯運算符。
正例:使用 if (x < 628) 來表達 x 小於 628。
反例:使用 if (!(x >= 628)) 來表達 x 小於 628。
7-8.接口入參保護,這種場景常見的是用做批量操做的接口。??
參考:
7-9.下列情形,須要進行參數校驗:
1) 調用頻次低的方法。
2) 執行時間開銷很大的方法。此情形中,參數校驗時間幾乎能夠忽略不計,但若是由於參數錯誤致使中間執行回退,或者錯誤,那得不償失。
3) 須要極高穩定性和可用性的方法。
4) 對外提供的開放接口,不論是 RPC/API/HTTP 接口。
5) 敏感權限入口。
7-10.下列情形,不須要進行參數校驗:
1) 極有可能被循環調用的方法。但在方法說明裏必須註明外部參數檢查要求。
2) 底層調用頻度比較高的方法。畢竟是像純淨水過濾的最後一道,參數錯誤不太可能到底層纔會暴露問題。通常 DAO 層與 Service 層都在同一個應用中,部署在同一臺服務器中,因此 DAO 的參數校驗,能夠省略。
3) 被聲明成 private 只會被本身代碼所調用的方法,若是可以肯定調用方法的代碼傳入參數已經作過檢查或者確定不會有問題,此時能夠不校驗參數。
八.註釋規約
8-1.類、類屬性、類方法的註釋必須使用 Javadoc 規範,使用/**內容*/格式,不得使用// xxx 方式。
8-2.全部的抽象方法(包括接口中的方法)必需要用 Javadoc 註釋、除了返回值、參數、異常說明外,還必須指出該方法作什麼事情,實現什麼功能。
說明:對子類的實現要求,或者調用注意事項,請一併說明。
8-3.全部的類都必須添加建立者和建立日期。
8-4.方法內部單行註釋,在被註釋語句上方另起一行,使用//註釋。方法內部多行註釋使用/* */註釋,注意與代碼對齊。
8-5.全部的枚舉類型字段必需要有註釋,說明每一個數據項的用途。
推薦:
8-6.與其「半吊子」英文來註釋,不如用中文註釋把問題說清楚。專有名詞與關鍵字保持英文原文便可。
8-7.代碼修改的同時,註釋也要進行相應的修改,尤爲是參數、返回值、異常、核心邏輯等的修改。
參考:
8-8.謹慎註釋掉代碼。在上方詳細說明,而不是簡單地註釋掉。若是無用,則刪除。
8-9.對於註釋的要求:第1、可以準確反應設計思想和代碼邏輯;第2、可以描述業務含義,使別的程序員可以迅速瞭解到代碼背後的信息。
8-10.好的命名、代碼結構是自解釋的,語義清晰的代碼不須要額外的註釋。
8-11.特殊註釋標記,請註明標記人與標記時間。注意及時處理這些標記,經過標記掃描,常常清理此類標記。線上故障有時候就是來源於這些標記處的代碼。
1) 待辦事宜(TODO):( 標記人,標記時間,[預計處理時間])
表示須要實現,但目前還未實現的功能。這其實是一個 Javadoc 的標籤,目前的 Javadoc 尚未實現,但已經被普遍使用。只能應用於類,接口和方法(由於它是一個 Javadoc 標籤)。
2) 錯誤,不能工做(FIXME):(標記人,標記時間,[預計處理時間])
在註釋中用 FIXME 標記某代碼是錯誤的,並且不能工做,須要及時糾正的狀況。
九.其餘
9-1.在使用正則表達式時,利用好其預編譯功能,能夠有效加快正則匹配速度。
說明:不要在方法體內定義:Pattern pattern = Pattern.compile(「規則」);
9-2.velocity 調用 POJO 類的屬性時,建議直接使用屬性名取值便可,模板引擎會自動按規範調用 POJO 的 getXxx(),若是是 boolean 基本數據類型變量(boolean 命名不須要加 is前綴),會自動調用 isXxx()方法。
說明:注意若是是 Boolean 包裝類對象,優先調用 getXxx()的方法。
9-3.後臺輸送給頁面的變量必須加$!{var}——中間的感嘆號。
說明:若是 var 等於 null 或者不存在,那麼${var}會直接顯示在頁面上。
9-4.注意 Math.random() 這個方法返回是 double 類型,注意取值的範圍 0≤x<1(可以取到零值,注意除零異常),若是想獲取整數類型的隨機數,不要將 x 放大 10 的若干倍而後取整,直接使用 Random 對象的 nextInt 或者 nextLong 方法。
9-5.獲取當前毫秒數 System.currentTimeMillis(); 而不是 new Date().getTime();
說明:若是想獲取更加精確的納秒級時間值,使用 System.nanoTime()的方式。
在 JDK8 中,針對統計時間等場景,推薦使用 Instant 類。
推薦:
9-6.不要在視圖模板中加入任何複雜的邏輯。
9-7.任何數據結構的構造或初始化,都應指定大小,避免數據結構無限增加吃光內存。
9-8.及時清理再也不使用的代碼段或配置信息。
說明:對於垃圾代碼或過期配置,堅定清理乾淨,避免程序過分臃腫,代碼冗餘。
正例:對於暫時被註釋掉,後續可能恢復使用的代碼片段,在註釋代碼上方,統一規定使用三個斜槓(///)來講明註釋掉代碼的理由。