第一章 Java開發中通用的方法和準則javascript
建議1:不要在常量和變量中出現易混淆的字母;php
(i、l、1;o、0等)。前端
建議2:莫讓常量蛻變成變量;java
(代碼運行工程中不要改變常量值)。mysql
建議3:三元操做符的類型務必一致;linux
(不一致會致使自動類型轉換,類型提高int->float->double等)。程序員
建議4:避免帶有變長參數的方法重載;正則表達式
(變長參數的方法重載以後可能會包含原方法)。算法
建議5:別讓null值和空值威脅到變長方法;sql
(兩個都包含變長參數的重載方法,當變長參數部分空值,或者爲null值時,重載方法不清楚會調用哪個方法)。
建議6:覆寫變長方法也循規蹈矩;
(變長參數與數組,覆寫的方法參數與父類相同,不只僅是類型、數量,還包括顯示形式)。
建議7:警戒自增的陷阱;
(count=count++;操做時JVM首先將count原值拷貝到臨時變量區,再執行count加1,以後再將臨時變量區的值賦給count,因此count一直爲0或者某個初始值。C++中count=count++;與count++等效,而PHP與Java相似)。
建議8:不要讓舊語法困擾你;
(Java中拋棄了C語言中的goto語法,可是還保留了該關鍵字,只是不進行語義處理,const關鍵中一樣相似)。
建議9:少用靜態導入;
(Java5引入的靜態導入語法import static,使用靜態導入能夠減小程序字符輸入量,可是會帶來不少代碼歧義,省略的類約束太少,顯得程序晦澀難懂)。
建議10:不要在本類中覆蓋靜態導入的變量和方法;
(例如靜態導入Math包下的PI常量,類屬性中又定義了一個一樣名字PI的常量。編譯器的「最短路徑原則」將會選擇使用本類中的PI常量。本類中的屬性,方法優先。若是要變動一個被靜態導入的方法,最好的辦法是在原始類中重構,而不是在本類中覆蓋)。
建議11:養成良好的習慣,顯式聲明UID;
(顯式聲明serialVersionUID能夠避免序列化和反序列化中對象不一致,JVM根據serialVersionUID來判斷類是否發生改變。隱式聲明由編譯器在編譯的時候根據包名、類名、繼承關係等諸多因子計算得出,極其複雜,算出的值基本惟一)。
建議12:避免用序列化類在構造函數中爲不變量賦值;
(在序列化類中,不適用構造函數爲final變量賦值)(序列化規則1:若是final屬性是一個直接量,在反序列化時就會從新計算;序列化規則2:反序列化時構造函數不會執行;反序列化執行過程:JVM從數據流中獲取一個Object對象,而後根據數據流中的類文件描述信息(在序列化時,保存到磁盤的對象文件中包含了類描述信息,不是類)查看,發現是final變量,須要從新計算,因而引用Person類中的name值,而此時JVM又發現name沒有賦值(由於反序列化時構造函數不會執行),不能引用,因而它再也不初始化,保持原始值狀態。整個過程當中須要保持serialVersionUID相同)。
建議13:避免爲final變量複雜賦值;
(類序列化保存到磁盤上(或網絡傳輸)的對象文件包括兩部分:一、類描述信息:包括包路徑、繼承關係等。注意,它並非class文件的翻版,不記錄方法、構造函數、static變量等的具體實現。二、非瞬態(transient關鍵字)和非靜態(static關鍵字)的實例變量值。總結:反序列化時final變量在如下狀況下不會被從新賦值:一、經過構造函數爲final變量賦值;二、經過方法返回值爲final變量賦值;三、final修飾的屬性不是基本類型)。
建議14:使用序列化類的私有方法巧妙解決「部分屬性持久化問題」;
(部分屬性持久化問題解決方案1:把不須要持久化的屬性加上瞬態關鍵字(transient關鍵字)便可,可是會使該類失去了分佈式部署的功能。方案2:新增業務對象。方案3:請求端過濾。方案4:變動傳輸契約,即覆寫writeObject和readObject私有方法,在兩個私有方法體內完成部分屬性持久化)。
建議15:break萬萬不可忘;
(switch語句中,每個case匹配完都須要使用break關鍵字跳出,不然會依次執行完全部的case內容。)。
建議16:易變業務使用腳本語言編寫;
(腳本語言:都是在運行期解釋執行。腳本語言三大特性:一、靈活:動態類型;二、便捷:解釋型語言,不須要編譯成二進制,不須要像Java同樣生成字節碼,依靠解釋執行,作到不中止應用變動代碼;三、簡單:部分簡單。Java使用ScriptEngine執行引擎來執行JavaScript腳本代碼)。
建議17:慎用動態編譯;
(好處:更加自如地控制編譯過程。不多使用,緣由:靜態編譯可以完成大部分工做甚至所有,即便須要使用,也有很好的替代方案,如JRuby、Groovy等無縫的腳本語言。動態編譯註意如下4點:一、在框架中謹慎使用:debug困難,成本大;二、不要在要求高性能的項目中使用:須要一個編譯過程,比靜態編譯多了一個執行環節;三、動態編譯要考慮安全問題:防止惡意代碼;四、記錄動態編譯過程)。
建議18:避免instanceof非預期結果;
(instanceof用來判斷一個對象是不是一個類的實例,只能用於對象的判斷,不能用於基本類型的判斷(編譯不經過),instanceof操做符的左右操做數必須有繼承或實現關係,不然編譯會失敗。例:null instanceof String返回值是false,instanceof特有規則,若左操做數是null,結果就直接返回false,再也不運算右操做數是什麼類)。
建議19:斷言絕對不是雞肋;
(防護式編程中常用斷言(Assertion)對參數和環境作出判斷。斷言是爲調試程序服務的。兩個特性:一、默認assert不啓用;二、assert拋出的異常AssertionError是繼承自Error的)。
建議20:不要只替換一個類;
(發佈應用系統時禁止使用類文件替換方式,總體WAR包發佈纔是徹底之策)(Client類中調用了Constant類中的屬性值,若是更改了Constant常量類屬性的值,從新編譯替換。而不改變或者替換Client類,則Client中調用的Constant常量類的屬性值並不會改變。緣由:對於final修飾的基本類型和String類型,編譯器會認爲它是穩定態(Immutable Status),因此在編譯時就直接把值編譯到字節碼中了,避免了再運行期引用,以提升代碼的執行效率。而對於final修飾的類(即非基本類型),編譯器認爲它是不穩定態(Mutable Status),在編譯時創建的則是引用關係(該類型也叫做Soft Final),若是Client類引入的常量是一個類或實例,即便不從新編譯也會輸出最新值)。
第二章 基本類型
建議21:用偶判斷,不用奇判斷;
(不要使用奇判斷(i%2 == 1 ? "奇數" : "偶數"),使用偶判斷(i%2 == 0 ? "偶數" : "奇數")。緣由Java中的取餘(%標識符)算法:測試數據輸入1 2 0 -1 -2,奇判斷的時候,當輸入-1時,也會返回偶數。
)。
建議22:用整數類型處理貨幣;
(不要使用float或者double計算貨幣,由於在計算機中浮點數「有可能」是不許確的,它只能無限接近準確值,而不能徹底精確。不能使用計算機中的二進制位來表示如0.4等的浮點數。解決方案:一、使用BigDecimal(優先使用);二、使用整型)。
建議23:不要讓類型默默轉換;
(基本類型轉換時,使用主動聲明方式減小沒必要要的Bug)
以上兩句在參與運算時會溢出,由於Java是先運算後再進行類型轉換的。由於dis2的三個運算參數都是int類型,三者相乘的結果也是int類型,可是已經超過了int的最大值,因此越界了。解決方法,在運算參數60後加L便可。
建議24:邊界、邊界、仍是邊界;
(數字越界是檢驗條件失效,邊界測試;檢驗條件if(order>0 && order+cur<=LIMIT),輸入的數大於0,加上cur的值以後溢出爲負值,小於LIMIT,因此知足條件,但不符合要求)。
建議25:不要讓四捨五入虧了一方;
(Math.round(10.5)輸出結果11;Math.round(-10.5)輸出結果-10。這是由於Math.round採用的舍入規則所決定的(採用的是正無窮方向舍入規則),根據不一樣的場景,慎重選擇不一樣的舍入模式,以提升項目的精準度,減小算法損失)。
建議26:提防包裝類型的null值;
(泛型中不能使用基本類型,只能使用包裝類型,null執行自動拆箱操做會拋NullPointerException異常,由於自動拆箱是經過調用包裝對象的intValue方法來實現的,而訪問null的intValue方法會報空指針異常。謹記一點:包裝類參與運算時,要作null值校驗,即(i!=null ? i : 0))。
建議27:謹慎包裝類型的大小比較;
(大於>或者小於<比較時,包裝類型會調用intValue方法,執行自動拆箱比較。而==等號用來判斷兩個操做數是否有相等關係的,若是是基本類型則判斷數值是否相等,若是是對象則判斷是不是一個對象的兩個引用,也就是地址是否相等。經過兩次new操做產生的兩個包裝類型,地址確定不相等)。
建議28:優先使用整型池;
(自動裝箱是經過調用valueOf方法來實現的,包裝類的valueOf生成包裝實例能夠顯著提升空間和時間性能)valueOf方法實現源碼:
cache是IntegerCache內部類的一個靜態數組,容納的是-128到127之間的Integer對象。經過valueOf產生包裝對象時,若是int參數在-128到127之間,則直接從整型池中得到對象,不在該範圍的int類型則經過new生成包裝對象。在判斷對象是否相等的時候,最好是利用equals方法,避免「==」產生非預期結果。
建議29:優先選擇基本類型;
(int參數先加寬轉變成long型,而後自動轉換成Long型。Integer.valueOf(i)參數先自動拆箱轉變爲int類型,與以前相似)。
建議30:不要隨便設置隨機種子;
(若非必要,不要設置隨機數種子)(Random r = new Random(1000);該代碼中1000即爲隨機種子。在同一臺機器上,無論運行多少次,所打印的隨機數都是相同的。在Java中,隨機數的產生取決於種子,隨機數和種子之間的關係聽從如下兩個規則:一、種子不一樣,產生不一樣的隨機數;二、種子相同,即便實例不一樣也產生相同的隨機數。Random類默認種子(無參構造)是System.nanoTime()的返回值,這個值是距離某一個固定時間點的納秒數,因此能夠產生隨機數。java.util.Random類與Math.random方法原理相同)。
第三章 類、對象及方法
建議31:在接口中不要存在實現代碼;
(能夠經過在接口中聲明一個靜態常量s,其值是一個匿名內部類的實例對象,能夠實現接口中存在實現代碼)。
建議32:靜態變量必定要先聲明後賦值;
(也能夠先使用後聲明,由於靜態變量是類初始化時首先被加載,JVM會去查找類中全部的靜態聲明,而後分配空間,分配到數據區(Data Area)的,它在內存中只有一個拷貝,不會被分配屢次,注意這時候只是完成了地址空間的分配尚未賦值,以後JVM會根據類中靜態賦值(包括靜態類賦值和靜態塊賦值)的前後順序來執行,後面的操做都是地址不變,值改變)。
建議33:不要覆寫靜態方法;
(一個實例對象有兩個類型:表面類型和實際類型,表面類型是聲明時的類型,實際類型是對象產生時的類型。對於非靜態方法,它是根據對象的實際類型來執行的,即執行了覆寫方法。而對於靜態方法,首先靜態方法不依賴實例對象,經過類名訪問;其次,能夠經過對象訪問靜態方法,若是經過對象訪問,JVM則會經過對象的表面類型查找到靜態方法的入口,繼而執行)。
建議34:構造函數儘可能簡化;
(經過new關鍵字生成對象時必然會調用構造函數。子類實例化時,首先會初始化父類(注意這裏是初始化,可不是生成父類對象),也就是初始化父類的變量,調用父類的構造函數,而後纔會初始化子類的變量,調用子類本身的構造函數,最後生成一個實例對象。構造函數太複雜有可能形成,對象使用時還沒完成初始化)。
建議35:避免在構造函數中初始化其餘類;
(有可能形成不斷的new新對象的死循環,直到棧內存被消耗完拋出StackOverflowError異常爲止)。
建議36:使用構造代碼塊精煉程序;
(四種類型的代碼塊:一、普通代碼塊:在方法後面使用「{}」括起來的代碼片斷;二、靜態代碼塊:在類中使用static修飾,並使用「{}」括起來的代碼片斷;三、同步代碼塊:使用synchronized關鍵字修飾,並使用「{}」括起來的代碼片斷,表示同一時間只能有一個縣城進入到該方法;四、構造代碼塊:在類中沒有任何的前綴或後綴,並使用「{}」括起來的代碼片斷。編譯器會把構造代碼塊插入到每一個構造函數的最前端。構造代碼塊的兩個特性:一、在每一個構造函數中都運行;二、在構造函數中它會首先運行)。
建議37:構造代碼塊會想你所想;
(編譯器會把構造代碼塊插入到每個構造函數中,有一個特殊狀況:若是遇到this關鍵字(也就是構造函數調用自身其餘的構造函數時)則不插入構造代碼塊。若是遇到super關鍵字,編譯器會把構造代碼塊插入到super方法以後執行)。
建議38:使用靜態內部類提升封裝性;
(Java嵌套內分爲兩種:一、靜態內部類;二、內部類;靜態內部類兩個優勢:增強了類的封裝性和提升了代碼的可讀性。靜態內部類與普通內部類的區別:一、靜態內部類不持有外部類的引用,在普通內部類中,咱們能夠直接訪問外部類的屬性、方法,即便是private類型也能夠訪問,這是由於內部類持有一個外部類的引用,能夠自由訪問。而靜態內部類,則只能夠訪問外部類的靜態方法和靜態屬性,其餘則不能訪問。二、靜態內部類不依賴外部類,普通內部類與外部類之間是相互依賴的關係,內部類不能脫離外部類實例,同聲同死,一塊兒聲明,一塊兒被垃圾回收器回收。而靜態內部類能夠獨立存在,即便外部類消亡了;三、普通內部類不能聲明static的方法和變量,注意這裏說的是變量,常量(也就是final static修飾的屬性)仍是能夠的,而靜態內部類形似外部類,沒有任何限制)。
建議39:使用匿名類的構造函數;
(List l2 = new ArrayList(){}; //定義了一個繼承於ArrayList的匿名類,只是沒有任何的覆寫方法而已
建議40:匿名類的構造函數很特殊;
(匿名類初始化時直接調用了父類的同參數構造器,而後再調用本身的構造代碼塊)
建議41:讓多重繼承成爲現實;
(Java中一個類能夠多種實現,但不能多重繼承。使用成員內部類實現多重繼承。內部類一個重要特性:內部類能夠繼承一個與外部類無關的類,保證了內部類的獨立性,正是基於這一點,多重繼承纔會成爲可能)。
建議42:讓工具類不可實例化;
(工具類的方法和屬性都是靜態的,不須要實例便可訪問。實現方式:將構造函數設置爲private,而且在構造函數中拋出Error錯誤異常)。
建議43:避免對象的淺拷貝;
(淺拷貝存在對象屬性拷貝不完全的問題。對於只包含基本數據類型的類能夠使用淺拷貝;而包含有對象變量的類須要使用序列化與反序列化機制實現深拷貝)。
建議44:推薦使用序列化實現對象的拷貝;
(經過序列化方式來處理,在內存中經過字節流的拷貝來實現深拷貝。使用此方法進行對象拷貝時需注意兩點:一、對象的內部屬性都是可序列化的;二、注意方法和屬性的特殊修飾符,好比final、static、transient變量的序列化問題都會影響拷貝效果。一個簡單辦法,使用Apache下的commons工具包中的SerializationUtils類,直接使用更加簡潔方便)。
建議45:覆寫equals方法時不要識別不出本身;
(須要知足p.equals(p)放回爲真,自反性)。
建議46:equals應該考慮null值情景;
(覆寫equals方法時須要判一下null,不然可能產生NullPointerException異常)。
建議47:在equals中使用getClass進行類型判斷;
(使用getClass方法來代替instanceof進行類型判斷)。
建議48:覆寫equals方法必須覆寫hashCode方法;
(須要兩個相同對象的hashCode方法返回值相同,因此須要覆寫hashCode方法,若是不覆寫,兩個不一樣對象的hashCode確定不同,簡單實現hashCode方法,調用org.apache.commons.lang.builder包下的Hash碼生成工具HashCodeBuilder)。
建議49:推薦覆寫toString方法;
(原始toString方法顯示不人性化)。
建議50:使用package-info類爲包服務;
(package-info類是專門爲本包服務的,是一個特殊性主要體如今3個方面:一、它不能隨便被建立;二、它服務的對象很特殊;三、package-info類不能有實現代碼;package-info類的做用:一、聲明友好類和包內訪問常量;二、爲在包上標註註解提供便利;三、提供包的總體註釋說明)。
建議51:不要主動進行垃圾回收;
(主動進行垃圾回收是一個很是危險的動做,由於System.gc要中止全部的響應(Stop 天河world),才能檢查內存中是否有可回收的對象,全部的請求都會暫停)。
第四章 字符串
建議52:推薦使用String直接量賦值;
(通常對象都是經過new關鍵字生成,String還有第二種生成方式,即直接聲明方式,如String str = "a";String中極力推薦使用直接聲明的方式,不建議使用new String("a")的方式賦值。緣由:直接聲明方式:建立一個字符串對象時,首先檢查字符串常量池中是否有字面值相等的字符串,若是有,則再也不建立,直接返回池中引用,若沒有則建立,而後放到池中,並返回新建對象的引用。使用new String()方式:直接聲明一個String對象是不檢查字符串常量池的,也不會吧對象放到池中。String的intern方法會檢查當前的對象在對象池中是否有字面值相同的引用對象,有則返回池中對象,沒有則放置到對象池中,並返回當前對象)。
建議53:注意方法中傳遞的參數要求;
(replaceAll方法傳遞的第一個參數是正則表達式)。
建議54:正確使用String、StringBuffer、StringBuilder;
(String使用「+」進行字符串鏈接,以前鏈接以後會產生一個新的對象,因此會不斷的建立新對象,優化以後與StringBuilder和StringBuffer採用一樣的append方法進行鏈接,可是每一次字符串拼接都會調用一次toString方法,因此會很耗時。StringBuffer與StringBuilder基本相同,只是一個字符數組的在擴容而已,都是可變字符序列,不一樣點是:StringBuffer是線程安全的,StringBuilder是線程不安全的)。
建議55:注意字符串的位置;
(在「+」表達式中,String字符串具備最高優先級)(Java對加號「+」的處理機制:在使用加號進行計算的表達式中,只要遇到String字符串,則全部的數據都會轉換爲String類型進行拼接,若是是原始數據,則直接拼接,若是是對象,則調用toString方法的返回值而後拼接)。
建議56:自由選擇字符串拼接方式;
(字符串拼接有三種方法:加號、concat方法及StringBuilder(或StringBuffer)的append方法。字符串拼接性能中,StringBuilder的append方法最快,concat方法次之,加號最慢。緣由:一、「+」方法拼接字符串:雖然編譯器對字符串的加號作了優化,使用StringBuidler的append方法進行追加,可是與純粹使用StringBuilder的append方法不一樣:一是每次循環都會建立一個StringBuilder對象,二是每次執行完都要調用toString方法將其準換爲字符串--toString方法最耗時;二、concat方法拼接字符串:就是一個數組拷貝,可是每次的concat操做都會新建立一個String對象,這就是concat速度慢下來的真正緣由;三、append方法拼接字符串:StringBuidler的append方法直接由父類AbstractStringBuilder實現,整個append方法都在作字符數組處理,沒有新建任何對象,因此速度快)。
建議57:推薦在複雜字符串操做中使用正則表達式;
(正則表達式是惡魔,威力強大,但難以控制)。
建議58:強烈建議使用UTF編碼;
(一個系統使用統一的編碼)。
建議59:對字符串排序持一種寬容的心態;
(若是排序不是一個關鍵算法,使用Collator類便可。主要針對於中文)。
第五章 數組和集合
建議60:性能考慮,數組是首選;
(性能要求較高的場景中使用數組替代集合)(基本類型在棧內存中操做,對象在堆內存中操做。數組中使用基本類型是效率最高的,使用集合類會伴隨着自動裝箱與自動拆箱動做,因此性能相對差一些)。
建議61:如有必要,使用變長數組;
(使用Arrays.copyOf(datas,newLen)對原數組datas進行擴容處理)。
建議62:警戒數組的淺拷貝;
(經過Arrays.copyOf(box1,box1.length)方法產生的數組是一個淺拷貝,這與序列化的淺拷貝徹底相同:基本類型是直接拷貝值,其餘都是拷貝引用地址。數組中的元素沒有實現Serializable接口)。
建議63:在明確的場景下,爲集合指定初始容量;
(ArrayList集合底層使用數組存儲,若是沒有初始爲ArrayList指定數組大小,默認存儲數組大小長度爲10,添加的元素達到數組臨界值後,使用Arrays.copyOf方法進行1.5倍擴容處理。HashMap是按照倍數擴容的,Stack繼承自Vector,所採用擴容規則的也是翻倍)。
建議64:多種最值算法,適時選擇;
(最值計算時使用集合最簡單,使用數組性能最優,利用Set集合去重,使用TreeSet集合自動排序)。
建議65:避開基本類型數組轉換列表陷阱;
(原始類型數組不能做爲asList的輸入參數,不然會引發程序邏輯混亂)(基本類型是不能泛化的,在java中數組是一個對象,它是能夠泛化的。使用Arrays.asList(data)方法傳入一個基本類型數組時,會將整個基本類型數組做爲一個數組對象存入,因此存入的只會是一個對象。JVM不可能輸出Array類型,由於Array是屬於java.lang.reflect包的,它是經過反射訪問數組元素的工具類。在Java中任何一個數組的類都是「[I」,由於Java並無定義數組這個類,它是編譯器編譯的時候生成的,是一個特殊的類)。
建議66:asList方法產生的List對象不可更改;
(使用add方法向asList方法生成的集合中添加元素時,會拋UnsupportedOperationException異常。緣由:asList生成的ArrayList集合並非java.util.ArrayList集合,而是Arrays工具類的一個內置類,咱們常用的List.add和List.remove方法它都沒有實現,也就是說asList返回的是一個長度不可變的列表。此處的列表只是數組的一個外殼,再也不保持列表動態變長的特性)。
建議67:不一樣的列表選擇不一樣的遍歷方法;
(ArrayList數組實現了RandomAccess接口(隨機存取接口),ArrayList是一個能夠隨機存取的列表。集合底層若是是基於數組實現的,實現了RandomAccess接口的集合,使用下標進行遍歷訪問性能會更高;底層使用雙向鏈表實現的集合,使用foreach的迭代器遍歷性能會更高)。
建議68:頻繁插入和刪除時使用LinkedList;
(ArrayList集合,每次插入或者刪除一個元素,其後的全部元素就會向後或者向前移動一位,性能很低。LinkedList集合插入時不須要移動其餘元素,性能高;修改元素,LinkedList集合比ArrayList集合要慢不少;添加元素,LinkedList與ArrayList集合性能差很少,LinkedList添加一個ListNode,而ArrayList則在數組後面添加一個Entry)。
建議69:列表相等只需關心元素數據;
(判斷集合是否相等時只須關注元素是否相等便可)(ArrayList與Vector都是List,都實現了List接口,也都繼承了AbstractList抽象類,其equals方法是在AbstractList中定義的。因此只要求兩個集合類實現了List接口就成,不關心List的具體實現類,只要全部的元素相等,而且長度也相等就代表兩個List是相等的,與具體的容量類型無關)。
建議70:子列表只是原列表的一個視圖;
(使用==判斷相等時,須要知足兩個對象地址相等,而使用equals判斷兩個對象是否相等時,只須要關注表面值是否相等。subList方法是由AbstractList實現的,它會根據是否是能夠隨機存取來提供不一樣的SubList實現方式,RandomAccessSubList是SubList子類,SubList類中subList方法的實現原理:它返回的SubList類是AbstractList的子類,其全部的方法如get、set、add、remove等都是在原始列表上的操做,它自身並無生成一個數組或是鏈表,也就是子列表只是原列表的一個視圖,全部的修改動做都反映在了原列表上)。
建議71:推薦使用subList處理局部列表;
(需求:要刪除一個ArrayList中的20-30範圍內的元素;將原列表轉換爲一個可變列表,而後使用subList獲取到原列表20到30範圍內的一個視圖(View),而後清空該視圖內的元素,便可在原列表中刪除20到30範圍內的元素)。
建議72:生成子列表後不要再操做原列表;
(subList生成子列表後,使用Collections.unmodifiableList(list);保持原列表的只讀狀態)(利用subList生成子列表後,更改原列表,會形成子列表拋出java.util.ConcurrentModificationException異常。緣由:subList取出的列表是原列表的一個視圖,原數據集(代碼中的list變量)修改了,可是subList取出的子列表不會從新生成一個新列表(這點與數據庫視圖是不相同的),後面再對子列表操做時,就會檢測到修改計數器與預期的不相同,因而就拋出了併發修改異常)。
建議73:使用Comparator進行排序;
(Comparable接口能夠做爲實現類的默認排序法,Comparator接口則是一個類的擴展排序工具)(兩種數據排序實現方式:一、實現Comparable接口,必需要實現compareTo方法,通常由類直接實現,代表自身是可比較的,有了比較才能進行排序;二、實現Comparator接口,必須實現compare方法,Comparator接口是一個工具類接口:用做比較,它與原有類的邏輯沒有關係,只是實現兩個類的比較邏輯)。
建議74:不推薦使用binarySearch對列表進行檢索;
(indexOf與binarySearch方法功能相似,只是使用了二分法搜索。使用二分查找的首要條件是必需要先排序,否則二分查找的值是不許確的。indexOf方法直接就是遍歷搜尋。從性能方面考慮,binarySearch是最好的選擇)。
建議75:集合中的元素必須作到compareTo和equals同步;
(實現了compareTo方法,就應該覆寫equals方法,確保二者同步)(在集合中indexOf方法是經過equals方法的返回值判斷的,而binarySearch查找的依據是compareTo方法的返回值;equals是判斷元素是否相等,compareTo是判斷元素在排序中的位置是否相同)。
建議76:集合運算時使用更優雅的方式;
(一、並集:list1.addAll(list2); 二、交集:list1.retainAll(list2); 三、差集:list1.removeAll(list2); 四、無重複的並集:list2.removeAll(list1);list1.addAll(list2);)。
建議77:使用shuffle打亂列表;
(使用Collections.shuffle(tagClouds)打亂列表)。
建議78:減小HashMap中元素的數量;
(儘可能讓HashMap中的元素少許並簡單)(現象:使用HashMap存儲數據時,還有空閒內存,卻拋出了內存溢出異常;緣由:HashMap底層的數組變量名叫table,它是Entry類型的數組,保存的是一個一個的鍵值對。與ArrayList集合相比,HashMap比ArrayList多了一次封裝,把String類型的鍵值對轉換成Entry對象後再放入數組,這就多了40萬個對象,這是問題產生的第一個緣由;HashMap在插入鍵值對時,會作長度校驗,若是大於或等於閾值(threshold變量),則數組長度增大一倍。默認閾值是當前長度與加載因子的乘積,默認的加載因子(loadFactor變量)是0.75,也就是說只要HashMap的size大於數組長度的0.75倍時,就開始擴容。致使到最後,空閒的內存空間不足以增長一次擴容時就會拋出OutOfMemoryError異常)。
建議79:集合中的哈希碼不要重複;
(列表查找無論是遍歷查找、鏈表查找或者是二分查找都不夠快。最快的是Hash開頭的集合(如HashMap、HashSet等類)查找,原理:根據hashCode定位元素在數組中的位置。HashMap的table數組存儲元素特色:一、table數組的長度永遠是2的N次冪;二、table數組中的元素是Entry類型;三、table數組中的元素位置是不連續的;每一個Entry都有一個next變量,它會指向下一個鍵值對,用來鏈表的方式來處理Hash衝突的問題。若是Hash碼相同,則添加的元素都使用鏈表處理,在查找的時候這部分的性能與ArrayList性能差很少)。
建議80:多線程使用Vector或HashTable;
(Vector與ArrayList原理相似,只是是線程安全的,HashTable是HashMap的多線程版本。線程安全:基本全部的集合類都有一個叫快速失敗(Fail-Fast)的校驗機制,當一個集合在被多個線程修改並訪問時,就可能出現ConcurrentModificationException異常,這是爲了確保集合方法一致而設置的保護措施;實現原理是modCount修改計數器:若是在讀列表時,modCount發生變化(也就是有其餘線程修改)則會拋出ConcurrentModificationException異常。線程同步:是爲了保護集合中的數據不被髒讀、髒寫而設置的)。
建議81:非穩定排序推薦使用List;
(非穩定的意思是:常常須要改動;TreeSet集合中元素不可重複,且默認按照升序排序,是根據Comparable接口的compareTo方法的返回值肯定排序位置的。SortedSet接口(TreeSet實現了該接口)只是定義了在該集合加入元素時將其進行排序,並不能保證元素修改後的排序結果。所以TreeSet適用於不變量的集合數據排序,但不適合可變量的排序。對於可變量的集合,須要本身手動進行再排序)(SortedSet中的元素被修改後可能會影響其排序位置)。
建議82:由點及面,一頁知秋--集合你們族;
(一、List:實現List接口的集合主要有:ArrayList、LinkedList、Vector、Stack,其中ArrayList是一個動態數組,LinkedList是一個雙向鏈表,Vector是一個線程安全的動態數組,Stack是一個對象棧,遵循先進後出的原則;
二、Set:Set是不包含重複元素的集合,其主要的實現類有:EnumSet、HashSet、TreeSet,其中EnumSet是枚舉類型的專用Set,HashSet是以哈希碼決定其元素位置的Set,原理與HashMap類似,提供快速插入與查找方法,TreeSet是一個自動排序的Set,它實現了SortedSet接口;
三、Map:能夠分爲排序Map和非排序Map;排序Map爲TreeMap,根據Key值進行自動排序;非排序Map主要包括:HashMap、HashTable、Properties、EnumMap等,其中Properties是HashTable的子類,EnumMap則要求其Key必須是某一個枚舉類型;
4:Queue:分爲兩類,一類是阻塞式隊列,隊列滿了之後再插入元素會拋異常,主要包括:ArrayBlockingQueue、PriorityBlockingQueue、LinkedBlockingQueue,其中ArrayBlockingQueue是以數組方式實現的有界阻塞隊列;PriorityBlockingQueue是依照優先級組件的隊列;LinkedBlockingQueue是經過鏈表實現的阻塞隊列;另外一類是非阻塞隊列,無邊界的,只要內存容許,均可以追加元素,常用的是PriorityQueue類。還有一種是雙端隊列,支持在頭、尾兩端插入和移除元素,主要實現類是:ArrayDeque、LinkedBlockingDeque、LinkedList;
五、數組:數組能存儲基本類型,而集合不行;全部的集合底層存儲的都是數組;
六、工具類:數組的工具類是:java.util.Arrays和java.lang.reflect.array;集合的工具類是java.util.Collections;
七、擴展類:能夠使用Apache的commons-collections擴展包,也能夠使用Google的google-collections擴展包)。
第六章 枚舉和註解
建議83:推薦使用枚舉定義常量;
(在項目開發中,推薦使用枚舉常量替代接口常量和類常量)(常量分爲:類常量、接口常量、枚舉常量;枚舉常量優勢:一、枚舉常量更簡單;二、枚舉常量屬於穩態性(不容許發生越界);三、枚舉具備內置方法,values方法能夠獲取到全部枚舉值;四、枚舉能夠自定義方法)。
建議84:使用構造函數協助描述枚舉項;
(每一個枚舉項都是該枚舉的一個實例。能夠經過添加屬性,而後經過構造函數給枚舉項添加更多描述信息)。
建議85:當心switch帶來的空值異常;
(使用枚舉值做爲switch(枚舉類);語句的條件值時,須要對枚舉類進行判斷是否爲null值。由於Java中的switch語句只能判斷byte、short、char、int類型,JDK7能夠判斷String類型,使用switch語句判斷枚舉類型時,會根據枚舉的排序值匹配。若是傳入的只是null的話,獲取排序值須要調用如season.ordinal()方法時會拋出NullPointerException異常)。
建議86:在switch的default代碼塊中增長AssertionError錯誤;
(switch語句在使用枚舉類做爲判斷條件時,避免出現增長了一個枚舉項,而switch語句沒作任何修改,編譯不會出現問題,可是在運行期會發生非預期的錯誤。爲避免這種狀況出現,建議在default後直接拋出一個AssertionError錯誤。含義是:不要跑到這裏來,一跑到這裏來立刻就會報錯)。
建議87:使用valueOf前必須進行校驗;
(Enum.valueOf()方法會把一個String類型的名稱轉變爲枚舉項,也就是在枚舉項中查找出字面值與該參數相等的枚舉項。valueOf方法先經過反射從枚舉類的常量聲明中查找,若找到就直接返回,若找不到就拋出IllegalArgumentException異常)。
建議88:用枚舉實現工廠方法模式更簡潔;
(工廠方法模式是「建立對象的接口,讓子類決定實例化哪個類,並使一個類的實例化延遲到其子類」。枚舉實現工廠方法模式有兩種方法:一、枚舉非靜態方法實現工廠方法模式;二、經過抽象方法生成產品;優勢:一、避免錯誤調用的發生;二、性能好,使用便捷;三、減低類間耦合性)。
建議89:枚舉項的數量控制在64個之內;
(Java提供了兩個枚舉集合:EnumSet、EnumMap;EnumSet要求其元素必須是某一枚舉的枚舉項,EnumMap表示Key值必須是某一枚舉的枚舉項。因爲枚舉類型的實例數量固定而且有限,相對來講EnumSet和EnumMap的效率會比其餘Set和Map要高。Java處理EnumSet過程:當枚舉項小於等於64時,建立一個RegularEnumSet實例對象,大於64時創一個JumboEnumSet實例對象。RegularEnumSet是把每一個枚舉項編碼映射到一個long類型數字得每一位上,而JumboEnumSet則會先按照64個一組進行拆分,而後每一個組再映射到一個long類型的數字得每一位上)。
建議90:當心註解繼承;
(不經常使用的元註解(Meta-Annotation):@Inherited,它表示一個註解是否能夠自動被繼承)。
建議91:枚舉和註解結合使用威力更大;
(註解和接口寫法相似,都採用了關鍵字interface,並且都不能有實現代碼,常量定義默認都是public static final類型的等,他們的主要不一樣點:註解要在interface前加上@字符,並且不能繼承,不能實現)。
建議92:注意@Override不一樣版本的區別;
(@Override註解用於方法的覆寫上,它在編譯期有效,也就是Java編譯器在編譯時會根據該註解檢查方法是否真的是覆寫,若是不是就報錯,拒絕編譯。Java1.5版本中@Override是嚴格遵照覆寫的定義:子類方法與父類方法必須具備相同的方法名、輸入參數、輸出參數(容許子類縮小)、訪問權限(容許子類擴大),父類必須是一個類,不是是接口,不然不能算是覆寫。而在Java1.6就開放了不少,實現接口的方法也能夠加上@Override註解了。若是是Java1.6版本移植到Java1.5版本中時,須要刪除接口實現方法上的@Override註解)。
第七章 泛型和反射
建議93:Java的泛型是類型擦除的;
(加入泛型優勢:增強了參數類型的安全性,減小了類型的轉換。Java的泛型在編譯期有效,在運行期被刪除,也就是說全部的泛型參數類型在編譯後都會被清除掉。因此:一、泛型的class對象時是相同的;二、泛型數組初始化時不能聲明泛型類型;三、instanceof不容許存在泛型參數)。
建議94:不能初始化泛型參數和數組;
(泛型類型在編譯期被擦除,在類初始化時將沒法得到泛型的具體參數,因此泛型參數和數組沒法初始化,可是ArrayList卻能夠,由於ArrayList初始化是向上轉型變成了Object類型;須要泛型數組解決辦法:只聲明,再也不初始化,由構造函數完成初始化操做)。
建議95:強制聲明泛型的實際類型;
(沒法從代碼中推斷出泛型類型的狀況下,便可強制聲明泛型類型;方法:List<Integer> list2 = ArrayUtils.<Integer>asList();在輸入前定義這是一個Integer類型的參數)。
建議96:不一樣的場景使用不一樣的泛型通配符;
(Java泛型支持通配符(Wildcard),能夠單獨使用一個「?」表示任意類,也能夠使用extends關鍵字表示某一個類(接口)的子類型,還能夠使用super關鍵字表示某一個類(接口)的父類型。一、泛型結構只參與「讀」操做則限定上界(extends關鍵字);二、泛型結構只參與「寫」操做則限定下界(使用super關鍵字);三、若是一個泛型結構既用做「讀」操做也用做「寫」操做則使用肯定的泛型類型便可,如List<E>)。
建議97:警戒泛型是不能協變和逆變的;
(Java的泛型是不支持協變和逆變的,只是可以實現協變和逆變)(協變和逆變是指寬類型和窄類型在某種狀況下(如參數、泛型、返回值)替換或交換的特性。簡單地說,協變是用一個窄類型替換寬類型,而逆變則是用寬類型覆蓋窄類型。子類覆寫父類返回值類型比父類型變窄,則是協變;子類覆寫父類型的參數類型變寬,則是逆變。數組支持協變,泛型不支持協變)。
建議98:建議採用的順序是List<T>,List<?>,List<Object>;
(一、List<T>是肯定的某一個類型,編碼者知道它是一個類型,只是在運行期才肯定而已;二、List<T>能夠進行讀寫操做,List<?>是隻讀類型,由於編譯器不知道List中容納的是什麼類型的元素,沒法增長、修改,可是能刪除,List<Object>也能夠讀寫操做,只是此時已經失去了泛型存在的意義了)。
建議99:嚴格限定泛型類型採用多重界限;
(使用「&」符號鏈接多個泛型界限,如:<T extends Staff & Passenger>)。
建議100:數組的真實類型必須是泛型類型的子類型;
(有可能會拋出ClassCastException異常,toArray方法返回後會進行一次類型轉換,Object數組轉換成了String數組。因爲咱們沒法在運行期得到泛型類型的參數,所以就須要調用者主動傳入T參數類型)。
建議101:注意Class類的特殊性;
(Java處理的基本機制:先把Java源文件編譯成後綴爲class的字節碼文件,而後再經過ClassLoader機制把這些類文件加載到內存中,最後生成實例執行。Java使用一個元類(MetaClass)來描述加載到內存中的類數據,這就是Class類,它是一個描述類的類對象。Class類是「類中類」,具備特殊性:一、無構造函數,不能實例化,Class對象是在加載類時由Java虛擬機經過調用類加載器中的defineClass方法自動構建的;二、能夠描述基本類型,8個基本類型在JVM中並非一個對象,通常存在於棧內存中,可是Class類仍然能夠描述它們,例如能夠使用int.class表示int類型的類對象;三、其對象都是單例模式,一個Class的實例對象描述一個類,而且只描述一個類,反過來也成立,一個類只有一個Class實例對象。Class類是Java的反射入口,只有在得到了一個類的描述對象後才能動態地加載、調用,通常得到一個Class對象有三種途徑:一、類屬性方式,如String.class;二、對象的getClass方法,如new String().getClass();三、forName方法重載,如Class.forName("java.lang.String")。得到了Class對象後,就能夠經過getAnnotation()得到註解,經過個體Methods()得到方法,經過getConstructors()得到構造函數等)。
建議102:適時選擇getDeclaredXXX和getXXX;
(getMethod方法得到的是全部public訪問級別的方法,包括從父類繼承的方法,而getDeclaredMethod得到的是自身類的全部方法,包括公用方法、私有方法等,並且不受限於訪問權限。Java之因此這樣處理,是由於反射本意只是正常代碼邏輯的一種補充,而不是讓正常代碼邏輯產生翻天覆地的改動,因此public的屬性和方法最容易獲取,私有屬性和方法也能夠獲取,但要限定本類。若是須要列出全部繼承自父類的方法,須要先得到父類,而後調用getDeclaredMethods方法,以後持續遞歸)。
建議103:反射訪問屬性或方法是將Accessible設置爲true;
(經過反射方式執行方法時,必須在invoke以前檢查Accessible屬性。而Accessible屬性並非咱們語法層級理解的訪問權限,而是指是否更容易得到,是否進行安全檢查。Accessible屬性只是用來判斷是否須要進行安全檢查的,若是不須要則直接執行,這就能夠大幅度地提高系統性能。通過測試,在大量的反射狀況下,設置Accessible爲true能夠提高性能20倍以上)。
建議104:使用forName動態加載類文件;
(forName只是加載類,並不執行任何代碼)(動態加載(Dynamic Loading)是指在程序運行時加載須要的類庫文件,通常狀況下,一個類文件在啓動時或首次初始化時會被加載到內存中,而反射則能夠在運行時再決定是否要加載一個類,而後在JVM中加載並初始化。動態加載一般是經過Class.forName(String)實現。一個對象的生成必然會通過一下兩個步驟:一、加載到內存中生成Class的實例對象;二、經過new關鍵字生成實例對象;動態加載的意義:加載一個類即表示要初始化該類的static變量,特別是static代碼塊,在這裏咱們能夠作大量的工做,好比註冊本身,初始化環境等,這纔是咱們重點關注的邏輯)。
建議105:動態加載不適合數組;
(經過反射操做數組使用Array類,不要採用通用的反射處理API)(若是forName要加載一個類,那它首先必須是一個類--8個基本類型排除在外,不是具體的類;其次,它必須具備可追索的類路徑,不然會報ClassNotFoundException異常。在Java中,數組是一個很是特殊的類,雖然是一個類,但沒有定義類路徑。做爲forName參數時會拋出ClassNotFoundException異常,緣由是:數組雖然是一個類,在聲明時能夠定義爲String[],但編譯器編譯後會爲不一樣的數組類型生成不一樣的類,因此要想動態建立和訪問數組,基本的反射是沒法實現的)。
建議106:動態代理能夠使代理模式更加靈活;
(Java的反射框架提供了動態代理(Dynamic Proxy)機制,容許在運行期對目標類生成代理,避免重複開發。靜態代理是經過代理主題角色(Proxy)和具體主題角色(Real Subject)共同實現抽象主題角色(Subject)的邏輯的,只是代理主題角色把相關的執行邏輯委託給了具體主題角色而已。動態代理須要實現InvocationHandler接口,必需要實現invoke方法,該方法完成了對真實方法的調用)。
建議107:使用反射增長裝飾模式的普適性;
(裝飾模式(Decorator Pattern)的定義是「動態地給一個對象添加一些額外的職責。就增長功能來講,裝飾模式相比於生成子類更爲靈活」。比較通用的裝飾模式,只須要定義被裝飾的類及裝飾類便可,裝飾行爲由動態代理實現,實現了對裝飾類和被裝飾類的徹底解耦,提供了系統的擴展性)。
建議108:反射讓模板方法模式更強大;
(決定使用模板方法模式時,請嘗試使用反射方式實現,它會讓你的程序更靈活、更強大)(模板方法模式(Template Method Pattern)的定義是:定義一個操做中的算法骨架,將一些步驟延遲到子類中,使子類不改變一個算法的結構便可重定義該算法的某些特定步驟。簡單說,就是父類定義抽象模板做爲骨架,其中包括基本方法(是由子類實現的方法,而且在模板方法被調用)和模板方法(實現對基本方法的調度,完成固定的邏輯),它使用了簡單的繼承和覆寫機制。使用反射後,不須要定義任何抽象方法,只需定義一個基本方法鑑別器便可加載複合規則的基本方法)。
建議109:不須要太多關注反射效率;
(反射效率低是個真命題,但由於這一點而不使用它就是個假命題)(反射效率相對於正常的代碼執行確實低不少(經測試,相差15倍左右),可是它是一個很是有效的運行期工具類)。
第8章 異常
建議110:提倡異常封裝;(異常封裝有三方面的優勢:一、提升系統的友好性;二、提升系統的可維護性;三、解決Java異常機制自己的缺陷);
建議111:採用異常鏈傳遞異常;
(責任鏈模式(Chain of Responsibility),目的是將多個對象連城一條鏈,並沿着這條鏈傳遞該請求,直到有對象處理它爲止,異常的傳遞處理也應該採用責任鏈模式)。
建議112:受檢異常儘量轉化爲非受檢異常;
(受檢異常威脅到系統的安全性、穩定性、可靠性、正確性時、不能轉爲非受檢異常)(受檢異常(Checked Exception),非受檢異常(Unchecked Exception),受檢異常時正常邏輯的一種補償處理手段,特別是對可靠性要求比較高的系統來講,在某些條件下必須拋出受檢異常以便由程序進行補償處理,也就是說受檢異常有合理的存在理由。可是受檢異常有不足的地方:一、受檢異常使接口聲明脆弱;二、受檢異常是代碼的可讀性下降,一個方法增長了受檢異常,則必須有一個調用者對異常進行處理。受檢異常須要try..catch處理;三、受檢異常增長了開發工做量。避免以上受檢異常缺點辦法:將受檢異常轉化爲非受檢異常)。
建議113:不要在finally塊中處理返回值;
(在finally塊中加入了return語句會致使如下兩個問題:一、覆蓋了try代碼塊中的return返回值;二、屏蔽異常,即便throw出去了異常,異常線程會登記異常,可是當執行器執行finally代碼塊時,則會從新爲方法賦值,也就是告訴調用者「該方法執行正確」,沒有發生異常,因而乎,異常神奇的消失了)。
建議114:不要在構造函數中拋異常;
(Java異常機制有三種:一、Error類及其子類表示的是錯誤,它是不須要程序員處理的也不能處理的異常,好比VirtualMachineError虛擬機錯誤,ThreadDeath線程僵死等;二、RuntimeException類及其子類表示的是非受檢異常,是系統可能拋出的異常,程序員能夠去處理,也能夠不處理,最經典的是NullPointerException空指針異常和IndexOutOfBoundsException越界異常;三、Exception類及其子類(不包含非受檢異常)表示的是受檢異常,這是程序員必需要處理的異常,不處理則程序不能經過編譯,好比IOException表示I/O異常,SQLException數據庫訪問異常。一個對象的建立過程要通過內存分配、靜態代碼初始化、構造函數執行等過程,構造函數中是否容許拋出異常呢?從Java語法上來講,徹底能夠,三類異常均可以,可是從系統設計和開發的角度分析,則儘可能不要在構造函數中拋出異常)。
建議115:使用Throwable得到棧信息;
(在出現異常時(或主動聲明一個Throwable對象時),JVM會經過fillInStackTrace方法記錄下棧信息,而後生成一個Throwable對象,這樣就能知道類間的調用順序、方法名稱以及當前行號等)。
建議116:異常只爲異常服務;
(異常本來是正常邏輯的一個補充,但有時候會被當前主邏輯使用。異常做爲主邏輯有問題:一、異常判斷下降了系統性能;二、下降了代碼的可讀性,只有詳細瞭解valueOf方法的人才能讀懂這樣的代碼,由於valueOf拋出的是一個非受檢異常;三、隱藏了運行期可能產生的錯誤,catch到異常,但沒有作任何處理)。
建議117:多使用異常,把性能問題放一邊;
(new一個IOException會被String慢5倍:由於它要執行fillInStackTrace方法,要記錄當前棧的快照,而String類則是直接申請一個內存建立對象。並且,異常類是不能緩存的。可是異常是主邏輯的例外邏輯,會讓方法更符合實際的處理邏輯,同時使主邏輯更加清晰,可以讓正常代碼和異常代碼分離、能快速查找問題(棧信息快照)等)。
第9章 多線程和併發
建議118:不推薦覆寫start方法;
(繼承自Thread類的多線程類沒必要覆寫start方法。本來的start方法中,調用了本地方法start0,它實現了啓動線程、申請棧內存、運行run方法、修改線程狀態等職責,線程管理和棧內存管理都是由JVM實現的,若是覆蓋了start方法,也就是撤銷了線程管理和棧內存管理的能力。因此除非必要,否則不要覆寫start方法,即便須要覆寫start方法,也須要在方法體內加上super.start調用父類中的start方法來啓動默認的線程操做)。
建議119:啓動線程前stop方法是不可靠的;
(現象:使用stop方法中止一個線程,而stop方法在此處的目的不是中止一個線程,而是設置線程爲不可啓用狀態。可是運行結果出現奇怪現象:部分線程仍是啓動了,也就是在某些線程(沒有規律)中的start方法正常執行了。在不符合判斷規則的狀況下,不可啓用狀態的線程仍是啓用了,這是線程啓動(start方法)一個缺陷。Thread類的stop方法會根據線程狀態來判斷是終結線程仍是設置線程爲不可運行狀態,對於未啓動的線程(線程狀態爲NEW)來講,會設置其標誌位爲不可啓動,而其餘的狀態則是直接中止。start方法源碼中,start0方法在stop0方法以前,也就是說即便stopBeforeStart爲true(不可啓動),也會先啓動一個線程,而後再stop0結束這個線程,而罪魁禍首就在這裏!因此不要使用stop方法進行狀態的設置)。
建議120:不適用stop方法中止線程;
(線程啓動完畢後,須要中止,Java只提供了一個stop方法,可是不建議使用,有如下三個問題:一、stop方法是過期的;二、stop方法會致使代碼邏輯不完整,stop方法是一種「惡意」的中斷,一旦執行stop方法,即終止當前正在運行的線程,無論線程邏輯是否完整,這是很是危險的,覺得stop方法會清除棧內信息,結束該線程,可是可能該線程的一段邏輯很是重,好比子線程的主邏輯、資源回收、情景初始化等,由於stop線程了,這些都不會再執行。子線程執行到何處會被關閉很難定位,這爲之後的維護帶來了不少麻煩;三、stop方法會破壞原子邏輯,多線程爲了解決共享資源搶佔的問題,使用了鎖概念,避免資源不一樣步,可是stop方法會丟棄全部的鎖,致使原子邏輯受損。Thread提供的interrupt中斷線程方法,它不能終止一個正在執行着的線程,它只是修改中斷標誌惟一。總之,指望終止一個正在運行的線程,不能使用stop方法,須要自行編碼實現。若是使用線程池(好比ThreadPoolExecutor類),那麼能夠經過shutdown方法逐步關閉池中的線程)。
建議121:線程優先級只使用三個等級;
(線程優先級推薦使用MIN_PRIORITY、NORM_PRIORITY、MAX_PRIORITY三個級別,不建議使用其餘7個數字)(線程的優先級(Priority)決定了線程得到CPU運行的機會,優先級越高,運行機會越大。事實:一、並非嚴格尊重線程優先級別來執行的,分爲10個級別;二、優先級差異越大,運行機會差異越大;對於Java來講,JVM調用操做系統的接口設置優先級,好比Windows是經過調用SetThreadPriority函數來設置的。不一樣操做系統線程優先級設置是不相同的,Windows有7個優先級,Linux有140個優先級,Freebsd有255個優先級。Java締造者也發現了該問題,因而在Thread類中設置了三個優先級,建議使用優先級常量,而不是1到10隨機的數字)。
建議122:使用線程異常處理器提高系統可靠性;
(能夠使用線程異常處理器來處理相關異常狀況的發生,好比當機自動重啓,大大提升系統的可靠性。在實際環境中應用注意如下三點:一、共享資源鎖定;二、髒數據引發系統邏輯混亂;三、內存溢出,線程異常了,但由該線程建立的對象並不會立刻回收,若是再從新啓動新線程,再建立一批新對象,特別是加入了場景接管,就危險了,有可能發生OutOfMemory內存泄露問題)。
建議123:volatile不能保證數據同步;
(volatile不能保證數據是同步的,只能保證線程可以得到最新值)(volatile關鍵字比較少用的緣由:一、Java1.5以前該關鍵字在不一樣的操做系統上有不一樣的表現,移植性差;二、比較難設計,並且誤用較多。在變量錢加上一個volatile關鍵字,能夠確保每一個線程對本地變量的訪問和修改都是直接與主內存交互的,而不是與本地線程的工做內存交互的,保證每一個線程都能得到最「新鮮」的變量值。可是volatile關鍵字並不能保證線程安全,它只能保證當前線程須要該變量的值時可以得到最新的值,而不能保證多個線程修改的安全性)。
建議124:異步運算考慮使用Callable接口;
(多線程應用的兩種實現方式:一種是實現Runnable接口,另外一種是繼承Thread類,這兩個方式都有缺點:run方法沒有返回值,不能拋出異常(歸根究竟是Runnable接口的缺陷,Thread也是實現了Runnable接口),若是須要知道一個線程的運行結果就須要用戶自行設計,線程類自己也不能提供返回值和異常。Java1.5開始引入了新的接口Callable,相似於Runnable接口,實現它就能夠實現多線程任務,實現Callable接口的類,只是代表它是一個可調用的任務,並不表示它具備多線程運算能力,仍是須要執行器來執行的)。
建議125:優先選擇線程池;
(Java1.5之前,實現多線程比較麻煩,須要本身啓動線程,並關注同步資源,防止出現線程死鎖等問題,Java1.5之後引入了並行計算框架,大大簡化了多線程開發。線程有五個狀態:新建狀態(New)、可運行狀態(Runnable,也叫做運行狀態)、阻塞狀態(Blocked)、等待狀態(Waiting)、結束狀態(Terminated),線程的狀態只能由新建轉變爲運行態後纔可能被阻塞或等待,最後終結,不可能產生本末倒置的狀況,好比想把結束狀態變爲新建狀態,則會出現異常。線程運行時間分爲三個部分:T1爲線程啓動時間;T2爲線程體運行時間;T3爲線程銷燬時間。每次建立線程都會通過這三個時間會大大增長系統的響應時間。T2是沒法避免的,只能經過優化代碼來下降運行時間。T1和T3均可以經過線程池(Thread Pool)來縮短期。線程池的實現涉及一下三個名詞:一、工做線程(Worker),線程池中的線程只有兩個狀態:可運行狀態和等待狀態;二、任務接口(Task),每一個任務必須實現的接口,以供工做線程調度器調度,它主要規定了任務的入口、任務執行完的場景處理、任務的執行狀態等。這裏的兩種類型的任務:具備返回值(或異常)的Callable接口任務和無返回值併兼容舊版本的Runnable接口任務;三、任務隊列(Work Queue),也叫做工做隊列,用於存放等待處理的任務,通常是BlockingQueue的實現類,用來實現任務的排隊處理。線程池的建立過程:建立一個阻塞隊列以容納任務,在第一次執行任務時闖將足夠多的線程(不超過許可線程數),並處理任務,以後每一個工做線程自行從任務隊列中得到任務,直到任務隊列中任務數量爲0爲止,此時,線程將處於等待狀態,一旦有任務加入到隊列中,即喚醒工做線程進行處理,實現線程的可複用性)。
建議126:適時選擇不一樣的線程池來實現;
(Java的線程池實現從根本上來講只有兩個:ThreadPoolExecutor類和ScheduledThreadPoolExecutor類,仍是父子關係。爲了簡化並行計算,Java還提供了一個Executors的靜態類,它能夠直接生成多種不一樣的線程池執行器,好比單線程執行器、帶緩衝功能的執行器等,歸根結底仍是以上兩個類的封裝類)。
建議127:Lock與synchronized是不同的;
(Lock類(顯式鎖)和synchronized關鍵字(內部鎖)用在代碼塊的併發性和內存上時的語義是同樣的,都是保持代碼塊同時只有一個線程具備執行權。顯式鎖的鎖定和釋放必須在一個try...finally塊中,這是爲了確保即便出現運行期異常也能正常釋放鎖,保證其餘線程可以順利執行。Lock鎖爲何不出現互斥狀況,全部線程都是同時執行的?緣由:這是由於對於同步資源來講,顯式鎖是對象級別的鎖,而內部鎖是類級別的鎖,也就是說Lock鎖是跟隨對象的,synchronized鎖是跟隨類的,更簡單地說把Lock定義爲多線程類的私有屬性是起不到資源互斥做用的,除非是把Lock定義爲全部線程共享變量。除了以上不一樣點以外,還有如下4點不一樣:一、Lock支持更細粒度的鎖控制,假設讀寫鎖分離,寫操做時不容許有讀寫操做存在,而讀操做時讀寫能夠併發執行,這一點內部鎖很難實現;二、Lock是無阻塞鎖,synchronized是阻塞鎖,線程A持有鎖,線程B也指望得到鎖時,若是爲Lock,則B線程爲等待狀態,若是爲synchronized,則爲阻塞狀態;三、Lock可實現公平鎖,synchronized只能是非公平鎖,什麼叫作非公平鎖?當一個線程A持有鎖,而線程B、C處於阻塞(或等待)狀態時,若線程A釋放鎖。JVM將從線程B、C中隨機選擇一個線程持有鎖並使其得到執行權,這叫作非公平鎖(由於它拋棄了先來後到的順序);若JVM選擇了等待時間最長的一個線程持有鎖,則爲公平鎖。須要注意的是,即便是公平鎖,JVM也沒法準確作到「公平」,在程序中不能以此做爲精確計算。顯式鎖默認是非公平鎖,但能夠在構造函數中加入參數true來聲明出公平鎖;四、Lock是代碼級的,synchronized是JVM級的,Lock是經過編碼實現的,synchronized是在運行期由JVM解釋的,相對來講synchronized的優化可能性更高,畢竟是在最核心不爲支持的,Lock的優化須要用戶自行考慮。相對來講,顯式鎖使用起來更加便利和強大,在實際開發中選擇哪一種類型的鎖就須要根據實際狀況考慮了:靈活、強大則選擇Lock,快捷、安全則選擇synchronized)。
建議128:預防線程死鎖;
(線程死鎖(DeadLock)是多線程編碼中最頭疼問題,也是最難重現的問題,由於Java是單進程多線程語言。要達到線程死鎖須要四個條件:一、互斥條件;二、資源獨佔條件;三、不剝奪條件;四、循環等待條件;按照如下兩種方式來解決:一、避免或減小資源貢獻;二、使用自旋鎖,若是在獲取自旋鎖時鎖已經有保持者,那麼獲取鎖操做將「自旋」在那裏,直到該自旋鎖的保持者釋放了鎖爲止)。
建議129:適當設置阻塞隊列長度;
(阻塞隊列BlockingQueue擴展了Queue、Collection接口,對元素的插入和提取使用了「阻塞」處理。可是BlockingQueue不可以自行擴容,若是隊列已滿則會報IllegalStateException:Queue full隊列已滿異常;這是阻塞隊列和非阻塞隊列一個重要區別:阻塞隊列的容量是固定的,非阻塞隊列則是變長的。阻塞隊列能夠在聲明時指定隊列的容量,若指定的容量,則元素的數量不可超過該容量,若不指定,隊列的容量爲Integer的最大值。有此區別的緣由是:阻塞隊列是爲了容納(或排序)多線程任務而存在的,其服務的對象是多線程應用,而非阻塞隊列容納的則是普通的數據元素。阻塞隊列的這種機制對異步計算是很是有幫助的,若是阻塞隊列已滿,再加入任務則會拒絕加入,並且返回異常,由系統自行處理,避免了異步計算的不可知性。能夠使用put方法,它會等隊列空出元素,再讓本身加入進去,不管等待多長時間都要把該元素插入到隊列中,可是此種等待是一個循環,會不停地消耗系統資源,當等待加入的元素數量較多時勢必會對系統性能產生影響。offer方法能夠優化一下put方法)。
建議130:使用CountDownLatch協調子線程;
(CountDownLatch協調子線程步驟:一個開始計數器,多個結束計數器:一、每個子線程開始運行,執行代碼到begin.await後線程阻塞,等待begin的計數變爲0;二、主線程調用begin的countDown方法,使begin的計數器爲0;三、每一個線程繼續運行;四、主線程繼續運行下一條語句,end的計數器不爲0,主線程等待;五、每一個線程運行結束時把end的計數器減1,標誌着本線程運行完畢;六、多個線程所有結束,end計數器爲0;七、主線程繼續執行,打印出結果。相似:領導安排了一個大任務給我,我一我的不可能完成,因而我把該任務分解給10我的作,在10我的所有完成後,我把這10個結果組合起來返回給領導--這就是CountDownLatch的做用)。
建議131:CyclicBarrier讓多線程齊步走;
(CyclicBarrier關卡可讓全部線程所有處於等待狀態(阻塞),而後在知足條件的狀況下繼續執行,這就比如是一條起跑線,無論是如何到達起跑線的,只要到達這條起跑線就必須等待其餘人員,待人員到齊後再各奔東西,CyclicBarrier關注的是匯合點的信息,而不在意以前或者以後作何處理。CyclicBarrier能夠用在系統的性能測試中,測試併發性)。
第10章 性能和效率
建議132:提高Java性能的基本方法;
(如何讓Java程序跑的更快、效率更高、吞吐量更大:一、不要在循環條件中計算,每循環一次就會計算一次,會下降系統效率;二、儘量把變量、方法聲明爲final static類型,加上final static修飾後,在類加載後就會生成,每次方法調用則再也不從新生成對象了;三、縮小變量的做用範圍,目的是加快GC的回收;四、頻繁字符串操做使用StringBuilder或StringBuffer;五、使用非線性檢索,使用binarySearch查找會比indexOf查找元素快不少,可是使用binarySearch查找時記得先排序;六、覆寫Exception的fillInStackTrace方法,fillInStackTrace方法是用來記錄異常時的棧信息的,這是很是耗時的動做,若是不須要關注棧信息,則能夠覆蓋,以提高性能;七、不創建冗餘對象)。
建議133:若非必要,不要克隆對象;
(克隆對象並不比直接生成對象效率高)(經過clone方法生成一個對象時,就會再也不執行構造函數了,只是在內存中進行數據塊的拷貝,看上去彷佛應該比new方法的性能好不少,但事實上,通常狀況下new生成的對象比clone生成的性能方面要好不少。JVM對new作了大量的系能優化,而clone方式只是一個冷僻的生成對象的方式,並非主流,它主要用於構造函數比較複雜,對象屬性比較多,經過new關鍵字建立一個對象比較耗時間的時候)。
建議134:推薦使用「望聞問切」的方式診斷性能;
(性能診斷遵循「望聞問切」,不可過分急躁)。
建議135:必須定義性能衡量標準;
(緣由:一、性能衡量標準是技術與業務之間的契約;二、性能衡量標誌是技術優化的目標)。
建議136:槍打出頭鳥--解決首要系統性能問題;
(解決性能優化要「單線程」小步前進,避免關注點過多而致使精力分散)(解決性能問題時,不要把全部的問題都擺在眼前,這隻會「擾亂」你的思惟,集中精力,找到那個「出頭鳥」,解決它,在大部分狀況下,一批性能問題都會迎刃而解)。
建議137:調整JVM參數以提高性能;(
四個經常使用的JVM優化手段:
一、調整堆內存大小,JVM兩種內存:棧內存(Stack)和堆內存(Heap),棧內存的特色是空間小,速度快,用來存放對象的引用及程序中的基本類型;而堆內存的特色是空間比較大,速度慢,通常對象都會在這裏生成、使用和消亡。棧空間由線程開闢,線程結束,棧空間由JVM回收,它的大小通常不會對性能有太大影響,可是它會影響系統的穩定性,超過棧內存的容量時,會拋StackOverflowError錯誤。能夠經過「java -Xss <size>」設置棧內存大小來解決。堆內存的調整不能太隨意,調整得過小,會致使Full GC頻繁執行,輕則致使系統性能急速降低,重則致使系統根本沒法使用;調整得太大,一則浪費資源(若設置了最小堆內存則能夠避免此問題),二則是產生系統不穩定的狀況,設置方法「java -Xmx1536 -Xms1024m」,能夠經過將-Xmx和-Xms參數值設置爲相同的來固定堆內存大小;
二、調整堆內存中各分區的比例,JVM的堆內存包括三部分:新生區(Young Generation Space)、養老區(Tenure Generation Space)、永久存儲區(Permanent Space 方法區),其中新生成的對象都在新生區,又分爲伊甸區(Eden Space)、倖存0區(Survivor 0 Space)和倖存1區(Survivor 1 Space),當在程序中使用了new關鍵字時,首先在Eden區生成該對象,若是Eden區滿了,則觸發minor GC,而後把剩餘的對象移到Survivor區(0區或者1區),若是Survivor取也滿了,則minor GC會再回收一次,而後再把剩餘的對象移到養老區,若是養老區也滿了,則會觸發Full GC(很是危險的動做,JVM會中止全部的執行,全部系統資源都會讓位給垃圾回收器),會對全部的對象過濾一遍,檢查是否有能夠回收的對象,若是仍是沒有的話,就拋出OutOfMemoryError錯誤。通常狀況下新生區與養老區的比例爲1:3左右,設置命令:「java -XX:NewSize=32m -XX:MaxNewSize=640m -XX:MaxPermSize=1280m -XX:NewRatio=5」,該配置指定新生代初始化爲32MB(也就是新生區最小內存爲32M),最大不超過640MB,養老區最大不超過1280MB,新生區和養老區的比例爲1:5.通常狀況下Eden Space : Survivor 0 Space : Survivor 1 Space == 8 : 1 : 1);
三、變動GC的垃圾回收策略,設置命令「java -XX:+UseParallelGC -XX:ParallelGCThreads=20」,這裏啓用了並行垃圾收集機制,而且定義了20個收集線程(默認的收集線程等於CPU的數量),這對多CPU的系統時很是有幫助的,能夠大大減小垃圾回收對系統的影響,提升系統性能;
四、更換JVM,若是全部的JVM優化都不見效,那就只有更換JVM了,比較流行的三個JVM產品:Java HotSpot VM、Oracle JRockit JVM、IBM JVM。
建議138:性能是個大「咕咚」;
(一、沒有慢的系統,只有不知足義務的系統;二、沒有慢的系統,只有架構不良的系統;三、沒有慢的系統,只有懶惰的技術人員;四、沒有慢的系統,只有不肯意投入的系統)。
第11章 開源世界
建議139:大膽採用開源工具;
(選擇開源工具和框架時要遵循必定的規則:一、普適性原則;二、惟一性原則;三、「大樹納涼」原則;四、精而專原則;五、高熱度原則)。
建議140:推薦使用Guava擴展工具包;
(Guava(石榴)是Google發佈的,其中包含了collections、caching、primitives support、concurrency libraries、common annotations、I/O等)。
建議141:Apache擴展包;
(Apache Commons通用擴展包基本上是每一個項目都會使用的,通常狀況下lang包用做JDK的基礎語言擴展。Apache Commons項目包含很是好用的工具,如DBCP、net、Math等)。
建議142:推薦使用Joda日期時間擴展包;
(Joda能夠很好地與現有的日期類保持兼容,在須要複雜的日期計算時使用Joda。日期工具類也能夠選擇date4j)。
建議143:能夠選擇多種Collections擴展;
(三個比較有個性的Collections擴展工具包:一、FastUtil,主要提供兩種功能:一種是限定鍵值類型的Map、List、Set等,另外一種是大容量的集合;二、Trove,提供了一個快速、高效、低內存消耗的Collection集合,而且還提供了過濾和攔截功能,同時還提供了基本類型的集合;三、lambdaj,是一個純淨的集合操做工具,它不會提供任何的集合擴展,只會提供對集合的操做,好比查詢、過濾、統一初始化等)。
第12章 思想爲源
建議144:提倡良好的代碼風格;
(良好的編碼風格包括:一、整潔;二、統一;三、流行;四、便捷,推薦使用Checkstyle檢測代碼是否遵循規範)。
建議145:不要徹底依靠單元測試來發現問題;
(單元測試的目的是保證各個獨立分隔的程序單元的正確性,雖然它可以發現程序中存在的問題(或缺陷、或錯誤),可是單元測試只是排查程序錯誤的一種方式,不能保證代碼中的全部錯誤都能被單元測試挖掘出來,緣由:一、單元測試不可能測試全部的場景(路徑);二、代碼整合錯誤是不可避免的;三、部分代碼沒法(或很難)測試;四、單元測試驗證的是編碼人員的假設)。
建議146:讓註釋正確、清晰、簡潔;
(註釋不是美化劑,而是催化劑,或爲優秀加分,或爲拙略減分)。
建議147:讓接口的職責保持單一;
(接口職責必定要單一,實現類職責儘可能單一)(單一職責原則(Single Responsibility Principle,簡稱SRP)有如下三個優勢:一、類的複雜性下降;二、可讀性和可維護性提升;三、下降變動風險)。
建議148:加強類的可替換性;
(Java的三大特徵:封裝、繼承、多態;說說多態,一個接口能夠有多種實現方式,一個父類能夠有多個子類,而且能夠把不一樣的實現或子類賦給不一樣的接口或父類。多態的好處很是多,其中一點就是加強了類的可替換性,可是單單一個多態特性,很難保證咱們的類是徹底能夠替換的,幸虧還有一個里氏替換原則來約束。里氏替換原則:全部引用基類的地方必須能透明地使用其子類的對象。通俗點講,只要父類型能出現的地方子類型就能夠出現,並且將父類型替換爲子類型還不會產生任何錯誤或異常,使用者可能根本就不須要知道是父類型仍是子類型。爲了加強類的可替換性,在設計類時須要考慮如下三點:一、子類型必須徹底實現父類型的方法;二、前置條件能夠被放大;三、後置條件能夠被縮小)。
建議149:依賴抽象而不是實現;
(此處的抽象是指物體的抽象,好比出行,依賴的是抽象的運輸能力,而不是具體的運輸交通工具。依賴倒置原則(Dependence Inversion Principle,簡稱DIP)要求實現解耦,保持代碼間的鬆耦合,提升代碼的複用率。DIP的原始定義包含三層含義:一、高層模塊不該該依賴底層模塊,二者都應該依賴其抽象;二、抽象不該該依賴細節;三、細節應該依賴抽象;DIP在Java語言中的表現就是:一、模塊間的依賴是經過抽象發生的,實現類之間不發生直接的依賴關係,其依賴關係是經過接口或抽象類產生的;二、接口或抽象類不依賴於實現類;三、實現類依賴接口或抽象類;更加精簡的定義就是:面向接口編程。實現模塊間的鬆耦合遵循規則:一、儘可能抽象;二、表面類型必須是抽象的;三、任何類都不該該從具體類派生;四、儘可能不要覆寫基類的方法;五、抽象不關注細節)。
建議150:拋棄7條不良的編碼習慣;
(一、自由格式的代碼;二、不使用抽象的代碼;三、彰顯個性的代碼;四、死代碼;五、冗餘代碼;六、拒絕變化的代碼;七、自覺得是的代碼)。
建議151:以技術人員自律而不是工人;
(20條建議:一、熟悉工具;二、使用IDE;三、堅持編碼;四、編碼前思考;五、堅持重構;六、多寫文檔;七、保持程序版本的簡單性;八、作好備份;九、作單元測試;十、不要重複發明輪子;十一、不要拷貝;十二、讓代碼充滿靈性;1三、測試自動化;1四、作壓力測試;1五、「剽竊」不可恥;1六、堅持向敏捷學習;1七、重裏更重面;1八、分享;1九、刨根問底;20、橫向擴展)。