之前整理了一部分,接下來就是阿里的java開發手冊編程規約的后面部分^_^ (五) 集合處理 1. 【強制】Map/Set的key為自定義對象時,必須重寫hashCode和equals。 正例:String重寫了hashCode和equals方法,所以我們可以非常愉快地使用String對象作為key來使用。 2. 【強制】ArrayList的subList結果不可強轉成ArrayList,否則會拋出ClassCastException異常:java.util.RandomaccessSubList cannot be cast to java.util.ArrayList ; 說明:subList 返回的是 ArrayList 的內部類 SubList,并不是 ArrayList ,而是 ArrayList 的一個視圖,對于SubList子列表的所有操作最終會反映到原列表上。 3. 【強制】在subList場景中,高度注意對原集合元素個數的修改,會導致子列表的遍歷、增加、刪除均產生ConcurrentModificationException 異常。 反例:直接使用toArray無參方法存在問題,此方法返回值只能是Object[]類,若強轉其它類型數組將出現ClassCastException錯誤。 正例:
List<String> list = new ArrayList<String>(2); list.add("guan"); list.add("bao"); String[] array = new String[list.size()]; array = list.toArray(array);說明:使用toArray帶參方法,入參分配的數組空間不夠大時,toArray方法內部將重新分配內存空間,并返回新數組地址;如果數組元素大于實際所需,下標為[ list.size() ]的數組元素將被置為null,其它數組元素保持原值,因此最好將方法入參數組大小定義與集合元素個數一致。 5. 【強制】使用工具類Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方法,它的add/remove/clear方法會拋出UnsupportedOperationException異常。 說明:asList的返回對象是一個Arrays內部類,并沒有實現集合的修改方法。Arrays.asList體現的是適配器模式,只是轉換接口,后臺的數據仍是數組。 String[] str = new String[] { “a”, “b” }; List list = Arrays.asList(str); 第一種情況:list.add(“c”); 運行時異常。 第二種情況:str[0]= “gujin”; 那么list.get(0)也會隨之修改。 6. 【強制】泛型通配符? extends T來接收返回的數據,此寫法的泛型集合不能使用add方法。 說明:蘋果裝箱后返回一個? extends Fruits對象,此對象就不能往里加任何水果,包括蘋果。 7. 【強制】不要在foreach循環里進行元素的remove/add操作。remove元素請使用Iterator方式,如果并發操作,需要對Iterator對象加鎖。 11 / 32 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比較結果相同。 9. 【推薦】集合初始化時,盡量指定集合初始值大小。 說明:ArrayList盡量使用ArrayList(int initialCapacity) 初始化。 10.【推薦】使用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值組合集合。 11.【推薦】高度注意Map類集合K/V能不能存儲null值的情況,如下表格:
反例:很多同學認為ConcurrentHashMap是可以置入null值。在批量翻譯場景中,子線程分發時,出現置入null值的情況,但主線程沒有捕獲到此異常,導致排查困難。 12.【參考】合理利用好集合的有序性(sort)和穩定性(order),避免集合的無序性(unsort)和不穩定性(unorder)帶來的負面影響。 說明:穩定性指集合每次遍歷的元素次序是一定的。有序性是指遍歷的結果是按某種比較規則依次排列的。如:ArrayList是order/unsort;HashMap是unorder/unsort;TreeSet是order/sort。 13.【參考】利用Set元素唯一的特性,可以快速對另一個集合進行去重操作,避免使用List的contains方法進行遍歷去重操作。 (六) 并發處理 1. 【強制】獲取單例對象要線程安全。在單例對象里面做操作也要保證線程安全。 說明:資源驅動類、工具類、單例工廠類都需要注意。 2. 【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。 說明:使用線程池的好處是減少在創建和銷毀線程上所花的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。 3. 【強制】SimpleDateFormat 是線程不安全的類,一般不要定義為static變量,如果定義為static,必須加鎖,或者使用DateUtils工具類。 正例:注意線程安全,使用DateUtils。亦推薦如下處理:
說明:如果是JDK8的應用,可以使用instant代替Date,Localdatetime代替Calendar,Datetimeformatter代替Simpledateformatter,官方給出的解釋:simple beautiful strong immutable thread-safe。 4. 【強制】高并發時,同步調用應該去考量鎖的性能損耗。能用無鎖數據結構,就不要用鎖;能鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。 5. 【強制】對多個資源、數據庫表、對象同時加鎖時,需要保持一致的加鎖順序,否則可能會造成死鎖。 說明:線程一需要對表A、B、C依次全部加鎖后才可以進行更新操作,那么線程二的加鎖順序也必須是A、B、C,否則可能出現死鎖。 6. 【強制】并發修改同一記錄時,避免更新丟失,要么在應用層加鎖,要么在緩存加鎖,要么在數據庫層使用樂觀鎖,使用version作為更新依據。 說明:如果每次訪問沖突概率小于20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數不得小于3次。 7. 【強制】多線程并行處理定時任務時,Timer運行多個TimeTask時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,使用ScheduledExecutorService則沒有這個問題。 8. 【強制】線程池不允許使用Executors去創建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。 說明:Executors各個方法的弊端: 1)newFixedThreadPool和newSingleThreadExecutor: 主要問題是堆積的請求處理隊列可能會耗費非常大的內存,甚至OOM。 2)newCachedThreadPool和newScheduledThreadPool: 主要問題是線程數最大數是Integer.MAX_VALUE,可能會創建數量非常多的線程,甚至OOM。 9. 【強制】創建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。 正例:
public class TimerTaskThread extends Thread { public TimerTaskThread(){ super.setName("TimerTaskThread"); … }10.【推薦】使用CountDownLatch進行異步轉同步操作,每個線程退出前必須調用countDown方法,線程執行代碼注意catch異常,確保countDown方法可以執行,避免主線程無法執行至countDown方法,直到超時才返回結果。 說明:注意,子線程拋出異常堆棧,不能在主線程try-catch到。 11.【推薦】避免Random實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一seed 導致的性能下降。 說明:Random實例包括java.util.Random 的實例或者 Math.random()實例。 正例:在JDK7之后,可以直接使用API ThreadLocalRandom,在 JDK7之前,可以做到每個線程一個實例。 12. 【推薦】通過雙重檢查鎖(double-checked locking)(在并發場景)實現延遲初始化的優化問題隱患(可參考 The “Double-Checked Locking is Broken” Declaration),推薦問題解決方案中較為簡單一種(適用于jdk5及以上版本),將目標屬性聲明為 volatile型(比如反例中修改helper的屬性聲明為private volatile Helper helper = null;); 13.【參考】volatile解決多線程內存不可見問題。對于一寫多讀,是可以解決變量同步問題,但是如果多寫,同樣無法解決線程安全問題。如果想取回count++數據,使用如下類實現:AtomicInteger count = new AtomicInteger(); count.addAndGet(1); count++操作如果是JDK8,推薦使用LongAdder對象,比AtomicLong性能更好(減少樂觀鎖的重試次數)。 14.【參考】注意HashMap的擴容死鏈,導致CPU飆升的問題。 15.【參考】ThreadLocal無法解決共享對象的更新問題,ThreadLocal對象建議使用static修飾。這個變量是針對一個線程內所有操作共有的,所以設置為靜態變量,所有此類實例共享此靜態變量 ,也就是說在類第一次被使用時裝載,只分配一塊存儲空間,所有此類的對象(只要是這個線程內定義的)都可以操控這個變量。 (七) 控制語句 1. 【強制】在一個switch塊內,每個case要么通過break/return來終止,要么注釋說明程序將繼續執行到哪一個case為止;在一個switch塊內,都必須包含一個default語句并且放在最后,即使它什么代碼也沒有。 2. 【強制】在if/else/for/while/do語句中必須使用大括號,即使只有一行代碼,避免使用下面的形式:if (condition) statements; 3. 【推薦】推薦盡量少用else, if-else的方式可以改寫成: if(condition){ … return obj; } // 接著寫else的業務邏輯代碼; 說明:如果使用要if-else if-else方式表達邏輯,【強制】請勿超過3層,超過請使用狀態設計模式。 4. 【推薦】除常用方法(如getXxx/isXxx)等外,不要在條件判斷中執行復雜的語句,以提高可讀性。 5. 【推薦】循環體中的語句要考量性能,以下操作盡量移至循環體外處理,如定義對象、變量、獲取數據庫連接,進行不必要的try-catch操作(這個try-catch是否可以移至循環體外)。 6. 【推薦】接口入參保護,這種場景常見的是用于做批量操作的接口。 7. 【參考】方法中需要進行參數校驗的場景: 1) 調用頻次低的方法。 2) 執行時間開銷很大的方法,參數校驗時間幾乎可以忽略不計,但如果因為參數錯誤導致中間執行回退,或者錯誤,那得不償失。 3) 需要極高穩定性和可用性的方法。 4) 對外提供的開放接口,不管是RPC/API/HTTP接口。 8. 【參考】方法中不需要參數校驗的場景: 1) 極有可能被循環調用的方法,不建議對參數進行校驗。但在方法說明里必須注明外部參數檢查。 2) 底層的方法調用頻度都比較高,一般不校驗。畢竟是像純凈水過濾的最后一道,參數錯誤不太可能到底層才會暴露問題。一般DAO層與Service層都在同一個應用中,部署在同一臺服務器中,所以DAO的參數校驗,可以省略。 3) 被聲明成private只會被自己代碼所調用的方法,如果能夠確定調用方法的代碼傳入參數已經做過檢查或者肯定不會有問題,此時可以不校驗參數。 (八) 注釋規約 1. 【強制】類、類屬性、類方法的注釋必須使用javadoc規范,使用/*內容/格式,不得使用//xxx方式。 說明:在IDE編輯窗口中,javadoc方式會提示相關注釋,生成javadoc可以正確輸出相應注釋;在IDE中,工程調用方法時,不進入方法即可懸浮提示方法、參數、返回值的意義,提高閱讀效率。 2. 【強制】所有的抽象方法(包括接口中的方法)必須要用javadoc注釋、除了返回值、參數、異常說明外,還必須指出該方法做什么事情,實現什么功能。 說明:如有實現和調用注意事項,請一并說明。 3. 【強制】所有的類都必須添加創建者信息。 4. 【強制】方法內部單行注釋,在被注釋語句上方另起一行,使用//注釋。方法內部多行注釋使用/* */注釋,注意與代碼對齊。 5. 【強制】所有的枚舉類型字段必須要有注釋,說明每個數據項的用途。 6. 【推薦】與其“半吊子”英文來注釋,不如用中文注釋把問題說清楚。專有名詞、關鍵字,保持英文原文即可。 反例:“TCP連接超時”解釋成“傳輸控制協議連接超時”,理解反而費腦筋。 7. 【推薦】代碼修改的同時,注釋也要進行相應的修改,尤其是參數、返回值、異常、核心邏輯等的修改。 說明:代碼與注釋更新不同步,就像路網與導航軟件更新不同步一樣,如果導航軟件嚴重滯后,就失去了導航的意義。 8. 【參考】注釋掉的代碼盡量要配合說明,而不是簡單的注釋掉。 說明:代碼被注釋掉有兩種可能性: 1)后續會恢復此段代碼邏輯。 2)永久不用。前者如果沒有備注信息,難以知曉注釋動機。后者建議直接刪掉(代碼倉庫保存了歷史代碼)。 9. 【參考】對于注釋的要求:第一、能夠準確反應設計思想和代碼邏輯;第二、能夠描述業務含義,使別的程序員能夠迅速了解到代碼背后的信息。完全沒有注釋的大段代碼對于閱讀者形同天書,注釋是給自己看的,即使隔很長時間,也能清晰理解當時的思路;注釋也是給繼任者看的,使其能夠快速接替自己的工作。 10. 【參考】好的命名、代碼結構是自解釋的,注釋力求精簡準確、表達到位。避免出現注釋的一個極端:過多過濫的注釋,代碼的邏輯一旦修改,修改注釋是相當大的負擔。 反例:
// put elephant into fridge put(elephant, fridge);11.【參考】特殊注釋標記,請注明標記人與標記時間。注意及時處理這些標記,通過標記掃描,經常清理此類標記。線上故障有時候就是來源于這些標記處的代碼。 1) 待辦事宜(TODO):( 標記人,標記時間,[預計處理時間]) 表示需要實現,但目前還未實現的功能。這實際上是一個javadoc的標簽,目前的javadoc還沒有實現,但已經被廣泛使用。只能應用于類,接口和方法(因為它是一個javadoc標簽)。 2) 錯誤,不能工作(FIXME):(標記人,標記時間,[預計處理時間]) 在注釋中用FIXME標記某代碼是錯誤的,而且不能工作,需要及時糾正的情況。 (九) 其它 1. 【強制】在使用正則表達式時,利用好其預編譯功能,可以有效加快正則匹配速度。 說明:不要在方法體內定義:Pattern pattern = Pattern.compile(規則); 2. 【強制】避免用Apache Beanutils進行屬性的copy。 說明:Apache BeanUtils性能較差,可以使用其他方案比如Spring BeanUtils, Cglib BeanCopier。 3. 【強制】velocity調用POJO類的屬性時,建議直接使用屬性名取值即可,模板引擎會自動按規范調用POJO的getXxx(),如果是boolean基本數據類型變量(注意,boolean命名不需要加is前綴),會自動調用isXxx()方法。 說明:注意如果是Boolean包裝類對象,優先調用getXxx()的方法。 4. 【強制】后臺輸送給頁面的變量必須加
有些內容感覺很合理,有些規約現在感覺很苛刻,但是畢竟這些都是Java開發經驗更加豐富的人總結的東西,先適應吧,希望對你的編程有幫助。
新聞熱點
疑難解答