改善java程序的151個建議

《編寫高質量代碼-改善java程序的151個建議》php

--秦小波html

第一章、開發中通用的方法和準則java

一、不要在常量和變量中出現易混淆的字母python

long a=0l; --> long a=0L;

二、莫讓常量蛻變成變量c++

static final int t=new Random().nextInt(); 

三、三元操做符的類型無比一致web

 int i=80;
 String s=String.valueOf(i<100?90:100);
 String s1=String.valueOf(i<100?90:100.0);
 System.out.print(s.equals(s1)); //false

 編譯器會進行類型轉換,將90轉爲90.0。有必定的原則,細節不表正則表達式

四、避免帶有變長參數的方法重載算法

public class MainTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println(PriceTool.calPrice(12, 1)); // 1
    }
}

class PriceTool {
    public static int calPrice(int price, int discount) {
        return 1;
    }
    public static int calPrice(int price, int... discount) {
        return 2;
    }
}

 編譯器會從最簡單的開始猜測,只要符合編譯條件的即採用spring

五、別讓null值和空值威脅到變長方法 數據庫

 

其中client.methodA("china")和client.methodA("china",null) 是編譯不經過的,由於編譯器不知道選擇哪一個方法

六、覆寫變長方法也循規蹈矩

 

 

 

重寫是正確的,由於父類的calprice編譯成字節碼後的形參是一個int類型的形參加上一個int數組類型的形參,子類的參數列表也是如此。

sub.fun(100,50) 編譯失敗,方法參數是數組,java要求嚴格類型匹配

 七、警戒自增的陷阱

 

輸出:0

步驟1:jvm把count(此時是0)值拷貝到臨時變量區

步驟2:count值加1,這時候count的值是1

步驟3:返回臨時變量區的值,0

步驟4:返回值賦值給count,此時count值被重置爲0

八、少用靜態導入

java5開始引入 import static ,其目的是爲了減小字符輸入量,提升代碼的可閱讀性。

舉個例子:

濫用靜態導入會使程序更難閱讀,更難維護。靜態導入後,代碼中就不用再寫類名了,可是咱們知道類是「一類事物的描述」,缺乏了類名的修飾,靜態屬性和靜態方法的表象意義能夠被無限放大,這會讓閱讀者很難弄清楚所謂何意。

舉個糟糕的例子:

對於靜態導入,必定要遵循兩個規則:

1)、不使用*通配符,除非是導入靜態常量類(只包含常量的類或接口)

2)、方法名是具備明確、清晰表象意義的工具類

九、不要在本類中覆蓋靜態導入的變量和方法

本地的方法和屬性會被使用。由於編譯器有最短路徑原則,以確保本類中的屬性、方法優先

十、養成良好習慣,顯示聲明UID

十一、避免用序列化類在構造函數中爲不變量賦值

序列化1.0

 

 

序列化2.0

此時反序列化,name:混世魔王

由於飯序列化時構造函數不會執行。jvm從數據流中獲取一個object對象,而後根據數據流中的類文件描述信息查看,發現時final變量,須要從新計算,因而引用person類中的name值,而辭職jvm又發現name居然沒有賦值,不能引用,因而再也不初始化,保持原值狀態。

 十二、避免爲final變量複雜賦值

反序列化時final變量在如下狀況不會被從新賦值

1)經過構造函數爲final變量賦值

2)經過方法返回值爲final變量賦值

3)final修飾的屬性不是基本類型

原理:

保存在磁盤(網絡傳輸)的對象文件包括兩部分

1)類描述信息

包括路徑、繼承關係、訪問權限、變量描述、變量訪問權限、方法簽名、返回值,以及變量的關聯類信息。與class文件不一樣的是,它不記錄方法、構造函數、statis變量等的具體實現。

2)非瞬態(transient關鍵字)和非靜態實例變量值

這裏的值若是是一個基本類型,就保存下來;若是是複雜對象,就連該對象和關聯類信息一塊兒保存,而且持續遞歸下去,其實仍是基本數據類型的保存。

也正是由於這兩點,一個持久化後的對象文件會比一個class類文件大不少

1三、使用序列化類的私有方法巧妙解決部分屬性持久化問題

舉個例子

一個服務像另外一個服務屏蔽類A的一個屬性x

class A{
int a;
int b;
int x;
}

 

可能有幾種解決方案

1)在屬性x前加上transient關鍵字(失去了分佈式部署的能力?todo)

2)新增業務對象類A1,去掉x屬性(符合開閉原則,並且對原系統沒有侵入型,可是增長代碼冗餘,且增長了工做量)

class A{
int a;
int b;
int x;
}

3)請求端過濾。得到A對象之後,過濾掉x屬性。(方案可行但不合規矩,本身服務的安全性須要外部服務承擔,不符合設計規範)

理想的解決方案:

用Serializable接口的兩個私有方法 writeObject和readObject,控制序列化和反序列化的過程

序列化回調:

java調用objectOutputStream類把一個對象轉換成流數據時,會經過反射檢查被序列化的類是否有writeObject方法,而且檢查其是否符合私有、無返回值的特性。如有,則會委託該方法進行對象序列化,若沒有,則由ObjectOutputStream按照默認規則繼續序列化。一樣,從流數據恢復成實例對象時,也會檢查是否有一個私有的readObject方法。

1四、switch-case-break 不要忽略break

1五、易變業務使用腳本語言編寫

java世界一直在遭受異種語言的入侵,好比php,ruby,groovy,js等。這種入侵者都有一個共同特徵:腳本語言,他們都在運行期解釋執行。爲何java這種強編譯型語言會須要這些腳本語言呢?那是由於腳本語言的三大特性:

1)靈活。腳本語言通常都是動態類型,能夠不用聲明變量類型而直接使用,也在能夠在運行期改變類型

2)便捷。腳本語言是一種解釋性語言,不須要編譯成二進制代碼,也不須要像java同樣生成字節碼。它的執行是依靠解釋器解釋的,所以在運行期變動帶啊嗎很是容易,並且不用中止應用

3)簡單。

腳本語言的這些特性是java所缺乏的,引入腳本語言可使java更強大,因而java6開始正式支持腳本語言。可是由於腳本語言比較多,java的開發者也很難肯定該支持哪一種語言,因而jcp提出了jsr223規範,只要符合該規範的語言均可以在java平臺上運行(默認支持js)

1六、慎用動態編譯(熱部署)

動態編譯一直是java的夢想,從java6版本開始支持動態編譯,能夠在運行期直接編譯.java文件,執行.class等,只要符合java規範均可以在運行期動態家在。

在使用動態編譯時,須要注意如下幾點:

1)在框架中謹慎使用

好比在Spring中,寫一個動態類,要讓它動態注入到spring容器中,這是須要花費老大功夫的

2)不要在要求高性能的項目使用

動態編譯畢竟須要一個編譯過程,與靜態編譯相比多了一個執行環節,所以在高性能項目中不要使用動態編譯。不過,若是在工具類項目中它則能夠很好的發揮其優越性,好比在idea中寫一個插件,就能夠很好地使用動態編譯,不用重啓便可實現運行、調試,很是方便。

3)考慮安全問題

若是你在web界面上提供了一個功能,容許上傳一個java文件而後運行,那就等於說「個人機器沒有密碼,你們都來看個人隱私吧」,這是很是典型的注入漏洞,只要上傳一個惡意java程序就可讓你全部的安全工做毀於一旦。

4)記錄動態編譯過程

建議記錄源文件、目標文件、編譯過程、執行過程等日誌,不只僅是爲了診斷,仍是爲了安全和審計,對java項目來講,空中編譯和運行是很不讓人放心的,留下這些依據能夠更好地優化程序

1七、避免instanceof非預期結果

instanceof是一個簡單的二元操做符,它是用來判斷一個對象是不是一個類實例的,兩側操做符須要有繼承或實現關係。

1)‘A’ instanceof Character :編譯不經過 ‘A’ 是一個char類型,也就是一個基本類型,不是一個對象,instanceof只能用於對象的判斷。

2)null instanceof String:編譯經過,返回false。這是instanceof特有的規則:若左操做符是null,結果直接返回false

3)(String)null instanceof String :編譯經過,返回false。null是一個萬用類型,也能夠說是沒類型,即便作類型轉換仍是個null

4)new Date()instanceof String:編譯不經過,date類和string沒有繼承或實現關係

5)new GenericClass<String>().isDateInstance("") :編譯經過,返回false。T是string,與date之間沒有繼承或實現關係,是由於java的泛型是爲編碼服務的,在編譯成字節碼時,T已是object類型了。傳遞的實參是string類型,也就是說T的表面類型是object,實際類型是string,這句話等價於object instance of date ,因此返回false。

1八、斷言絕對不是雞肋

在防護式編程中常常會用斷言對參數和環境作出判斷,避免程序因不當的輸入或錯誤的環境而產生邏輯異常,斷言在不少語言中都存在,c、c++、python都有不一樣的斷言表達形式。在java中斷言的使用是assert關鍵字,以下

assert <布爾表達式> :<錯誤信息>

在布爾表達式爲假時,拋出AssertionError錯誤,並附帶錯誤信息

兩個特性

1)assert默認是不開啓的

2)AssertionError是繼承自Error的。這是錯誤,不可恢復

不可以使用斷言的狀況:

1)在對外公開的方法中

2)在執行邏輯代碼的狀況下。由於生產環境是不開啓斷言的。避免由於環境的不一樣產生不一樣的業務邏輯

建議使用斷言的狀況:

1)在私有方法中,私有方法的使用者是本身,能夠更好的預防本身犯錯

2)流程控制中不可能到達的區域。若是到達則拋異常

3)創建程序探針。咱們可能會在一段程序中定義兩個變量,分別代碼兩個不一樣的業務含義,可是二者有固定的關係。例如 var1=var2*2,那咱們就能夠在程序中處處設‘樁’,斷言這二者的關係,若是不知足即代表程序已經出現了異常,業務也就沒有必要運行下去了

1九、不能只替換一個類

舉個例子:

若是在一個運行中項目,直接替換constans.class ,其中 maxage改成180。client中的輸入依然是150

緣由是

對於final修飾的基本類型和string類型,編譯器會認爲它是穩定態,因此在編譯時就直接把值編譯到字節碼中了,避免了在運行期引用,以提升代碼的執行效率。

對於final修飾的類,編譯器認爲它是不穩定態,在編譯時創建的則是引用關係(soft final),若是client類引入的常量是一個類或實例,即便不從新編譯也會輸出最新值

基本數據類型相關

2一、用偶判斷,不用奇判斷

i%2==1?奇數:偶數

這個邏輯是不對的,當i爲負數時計算錯誤。由於取餘的計算邏輯爲

int remainder(int a,int b){
      return a-a/b*b;
}

2二、用整數類型處理貨幣

在計算機中浮點數有多是不許確的,它只能無限接近準確值,而不能徹底精確。這是因爲浮點數的存儲規則決定的(略過)。

舉個例子:system.out.print(10.00-9.06)  :0.4000000000000036

有兩種解決方案:

1)BigDecimal

BigDecimal是專門爲彌補浮點數沒法精確計算的缺憾而設計的類,而且它自己也提供了加減乘除的經常使用數學算法。特別是與數據庫Decimal類型的字段映射時,BigDeciaml是最優的解決方案。

2)使用整型

把參與運算的值擴大100倍,並轉變爲整型,而後在展示時再縮小100倍。

2三、不要讓類型默默轉換

舉個例子:

太陽逛照射到地球上須要8分鐘,計算太陽到地球的距離。

long result=light_speed * 60 * 8;

輸出的結果是 -202888064 

緣由:java是先運算而後再進行類型轉換的,三者相乘,超過了int的最大值,因此其值是負值(溢出是負值的緣由看一下

正確的處理是 long result=light_speed * 60L * 8;

2四、數字邊界問題

        舉個例子:

         if(order+base<limit){...}

         當order+base足夠大時,超過了int的最大值,其值是負值,因此業務邏輯會有問題

2五、四捨五入問題

math.round(-10.5) 輸出 -10 這是math。round採用的舍入規則所決定的(採用的是正無窮方向舍入規則)

以上算法對於一個5000w存款的銀行來講,一年將損失10w。一個美國銀行家發現了此問題並提出了一個修正算法,叫作銀行家舍入的近似算法(規則不記錄了)。java5能夠直接用RoundingMode類提供的Round模式。與BigDecimal絕配。RoundingMode支持7種舍入模式:

遠離零方向舍入、趨向零方向舍入、向正無窮方向舍入、向負無窮方向舍入、最近數字舍入、銀行家算法

2六、提防包裝類型的null值

舉個例子。當list中有null元素,自動拆箱時調用intValue()會報空指針異常。

2七、謹慎包裝類型的大小比較

舉個例子。i==j false。Integer是引用類型

2八、優先使用整型池

Integer緩存了-128-127的Integer對象。因此經過裝箱(Integer.valueOf())得到的對象能夠複用。

 public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

 2九、優先選擇基本數據類型

自動裝箱有一個重要的原則:基本類型能夠先加寬,再轉變成寬類型的包裝類型,但不能直接轉變成寬類型的包裝類型。

舉個例子

兩次調用都是基本類型的方法

30、不要隨便設置隨機數種子

程序啓動後,生成的隨機數會不一樣。可是每次啓動程序,生成的都會是三個隨機數。產生隨機數和seed之間的關係以下:

1)種子不一樣,產生不一樣的隨機數

2)種子相同,即便實例不一樣也產生相同的隨機數

Random的默認種子(無參構造)是System.nanoTime()的返回值(jdk1.5之前是System.currentTimeMillis()),這個值是距離某一個固定時間點的納秒數,不一樣的操做系統和硬件有不一樣的固定時間點,隨機數天然也就不一樣了

 第三章 類、對象及方法

3一、在接口中不要存在實現代碼

其實是有這種可能的,可是千萬不要這樣寫

舉個例子

 3二、靜態變量必定要先聲明,後賦值

舉個例子:輸出1

 

靜態變量的初始化:先分配空間,再賦值

類初始化時會先先分配空間,再按照加載順序去賦值 :靜態的(變量、靜態塊)的加載順序是 從上到下

3三、不要覆寫靜態方法

在子類中構建與父類相同的方法名、輸入參數、輸出參數、訪問權限,而且父類、子類都是靜態方法,此種行爲叫作隱藏,它與重寫有兩點不一樣:

1)表現形式不一樣。@override能夠用於重寫,不能用於隱藏

2)指責不一樣。隱藏的目的是爲了拋棄父類靜態方法。重寫則是將父類的行爲加強或者減弱,延續父類的指責

3四、構造函數儘可能簡化

3五、避免在構造函數中初始化其餘類

1)更符合面向對象編程

2)類與類關係複雜,容易形成棧溢出

3六、使用構造代碼塊精煉程序

什麼是構造代碼塊

構造代碼塊的特性:在每一個構造函數中都運行,且會首先運行

3八、使用靜態內部類提升封裝性

1)提供封裝性

2)提升代碼可讀性

3九、使用匿名內部類的構造函數

舉個例子

        List l1=new ArrayList();  
        List l2=new ArrayList(){}; 
        List l3=new ArrayList(){{}};
        System.out.println(l1.getClass()==l2.getClass());//false 
        System.out.println(l1.getClass()==l3.getClass());//false
        System.out.println(l3.getClass()==l2.getClass());//false

l1:arraylist實例

l2:{}表示一個匿名內部類,可是沒有重寫任何方法,至關於匿名內部類的實例

l3:外層{}表示一個匿名內部類,可是沒有重寫任何方法,內層{}表示匿名內部類的初始化塊,能夠有多個。

40、匿名類的構造方法很特殊

 通常類默認都是調用父類的無參構造函數的,而匿名類由於沒有名字,只能由構造代碼塊代替,也就無所謂的有參和無參構造函數類,它在初始化時直接調用類父類的同參構造函數,而後再調用本身的構造代碼塊

4一、讓多重繼承成爲現實

使用內部類實現多繼承

4二、讓工具類不可實例化

java項目中使用的工具類很是多,好比jdk本身的工具類java.lang.math java.util.collections等都是咱們常常用到的。工具類的方法和屬性都是靜態的,不須要生成實例便可訪問,並且jdk也作了很好的處理,因爲不但願被初始化,因而就設置構造函數爲private。也能夠在構造函數中拋一個error。

4三、避免對象的淺拷貝

一個類實現類cloneable接口就表示它具有類被拷貝的能力,若是再重寫clone方法就會徹底具有拷貝能力。拷貝是在內存中進行的,因此在性能方面比直接經過new生成對象要快不少,特別是在大對象的生成上,這會使性能的提高很是顯著。可是object提供的默認對象拷貝是淺拷貝。

淺拷貝的規則:

1)基本類型

若是變量是基本類型,則拷貝其值

2)對象

拷貝地址引用

3)string字符串

這個比較特殊,拷貝的也是一個地址,是個引用。可是在修改時,它會從字符串池中從新生成新的字符串,原有的字符串對象保持不變,在此處咱們能夠認爲string是一個基本類型

4四、推薦使用序列化實現對象的拷貝

實現serializable接口,使用序列化實現對象的深拷貝。或者其餘序列化方式json等

4五、重寫equals方法時不要識別不出本身

一句話總結,equals知足自反性,傳遞性,對稱性,一致性規則 ,參考:http://www.javashuo.com/article/p-guhzjvpv-mk.html

 4六、重寫equals應該考慮null

一句話總結,equals知足自反性,傳遞性,對稱性,一致性規則 ,參考:http://www.javashuo.com/article/p-guhzjvpv-mk.html

4七、在equals中使用getclass進行類型判斷 

兩個不一樣的類,可能具有相同的屬性,致使equals相等

4八、重寫equals方法必須重寫hashcode方法

     參考:http://www.javashuo.com/article/p-guhzjvpv-mk.html

4九、推薦重寫tostring

50、使用package-info類爲包服務

java中有一個特殊的類:package-info類,它是專門爲本包服務的。package-info特性

1)它不能隨便被建立

不能經過new的形式建立。能夠在text建立,拷貝過來

2)它服務的對象很特殊

一個類是一類或一組事物的描述,但package-info是描述和記錄本包信息的

3)package-info類不能有代碼實現

package-info也會被編譯成package-info.class ,可是在package-info.java文件裏不能聲明package-info類。不能夠繼承,沒有接口...

package-info做用

1)聲明友好類和包內訪問常量

雖然它沒有編寫package-info的實現,可是package-info.class類文件仍是會生成。

2)爲在包上標註註解提供便利

好比咱們要寫一個註解,查看一個包下的全部對象,只要把註解標註到package-info文件中便可,並且不少開源項目也採用類此方法,好比struts2的@namespace、hibernate的@filterdef等

3)提供包的總體註釋說明

經過javadoc生成文檔時,會把這些說明做爲包文檔的首頁,讓讀者更容易對該包有一個總體的認識。固然在這點上它與package.htm的做用是相同的,不夠package-info能夠在代碼中維護文檔的完整性,而且能夠實現代賣與文檔的同步更新

5一、不要主動進行垃圾回收 

4、字符串

5二、推薦使用string直接量賦值

常量池

5三、注意方法中傳遞的參數要求

舉個例子:

string.replaceAll("","") 要求第一個參數傳的是正則表達式。若是傳了一些$($在正則中表示字符串的結束位置)等,會有異常

5四、正確使用string、stringbuffer、stringbuilder

5五、注意字符串的位置

java對加號的處理機制:在使用加號進行計算的表達式中,只要遇到string字符串,則全部的數據都會轉換爲string類型進行拼接,若是是對象,調用tostring方法的返回值拼接

string s=1+1+"a"; //2a  

5六、選擇適當的字符串拼接方法

1)+ :編譯器對字符串的加號作了優化,它會使用tringbuilder的append方法進行追加,而後經過tostring方法轉換成字符串

2)concat():數組拷貝,可是會會建立string對象

    public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }

3)stringbuffer、stringbuilder:數組拷貝

 public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

5七、推薦在複雜字符串操做中使用正則表達式

5八、統一編碼

5九、對字符串排序持一種寬容的心態

比較器通常是經過compareTo比較。該方法是先取得字符串的字符數組,而後一個個比較大小(減號操做符),也就是unicode碼值的比較。因此非英文排序會出現不許確的狀況。java推薦使用collator類進行排序

 public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }

第五章 數組與集合

60、性能考慮,數組是首選

6一、如有必要,使用可變數組

參考list擴容 

6二、警戒數組的淺拷貝

arrays.copyof, clone都是淺拷貝

6三、在明確的場景下,爲集合指定初始容量

6四、多種最值算法,實時選擇

一句話總結:沒必要追求最快算法,仍是要結合業務,找準側重點

6五、避開基本類型數組轉換列表陷阱

基本數據類型不能做爲aslist的輸入參數

輸出1

int類型不能泛型化。替換成Intger

6六、 aslist方法產生的list對象不可更改

  public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }
 private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {}
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }    
}

arraylist是arrays的靜態內部類,在父類聲明類add方法,拋出異常 。爲啥要設計成這樣?若是是不可變類推薦guava。immlist

 6七、不一樣的列表選擇不一樣的遍歷方法

兩種方式

1) foreach :shi iterator的變形用法。也就是須要先建立一個迭代器容器,而後屏蔽內部遍歷細節,對外提供hasnext等方法。

2) for(int i=0 )  採用下標方式遍歷列表

arraylist 實現類RandomAccess接口(隨機存取接口),這也就標誌着arraylist是一個能夠隨機存取的列表 。適合採用下標方式來訪問

linkedlist,雙向鏈表,兩個元素原本就是有關聯的,用foreach會高效

6八、頻繁插入和刪除時使用linkedlist

6九、列表相等只需關心元素數據

s1.equals(s2) 。二者都是list,equals方法是在abstractlist中定義的

70、子列表只是原列表的一個視圖

list接口提供來sublist方法,返回的子列表只是一個視圖,對子列表的操做至關於操做原列表

7一、推薦使用sublist處理局部列表

代碼比較簡潔

7二、生成子列表後不要再操做原列表(sublist)

checkForconmodification方法是用於檢測併發修改的。modcount是從子列表的構造函數中賦值的,其值等於生成子列表時的修改次數。由於在生成子列表後再修改原始列表modcount的值就不相等了。

   public void add(int index, E e) {
            rangeCheckForAdd(index);
            checkForComodification();
            parent.add(parentOffset + index, e);
            this.modCount = parent.modCount;
            this.size++;
        }
  private void checkForComodification() {
            if (ArrayList.this.modCount != this.modCount)
                throw new ConcurrentModificationException();
        }

7三、使用Comparator進行排序

7四、不推薦使用binarySearch對列表進行檢索

binarySearch基於二分算法。要求列表自己升序。推薦indexof

7五、集合中的元素必須作到compareTo和equals同步

好比說 indexOf()依賴equals方法查找,binarySearch則依賴compareTo方法查找

7六、集合運算時使用更優雅的方式

1)並集:list1.addAll(list2)

2) 交集:list.retainAll(list2)

3) 差級:list1.removeAll(list2)

4)無重複的並集:list1.removeAll(list2); list1.addAll(list2)

7七、使用shuffle打亂列表

7八、減小hashmap中元素的數量

哈?行吧。entry對象和2倍擴容 注意下內存使用就行

7九、集合彙總的哈希碼不要重複

map key 衝突。下降效率

80、多線程使用vector 、hashtable 

算了吧

8一、非穩定排序推薦使用list

原文是與treeset作對比的 

8二、有點及面,一葉知秋-集合你們族

第六章 枚舉和註解

8三、推薦使用枚舉定義常量

8四、使用構造函數協助描述枚舉項add code

8五、當心switch帶來的空值異常 

8六、在switch的default代碼快中增長assertionerror錯誤

switch代碼與枚舉之間沒有強制的約束關係,只是在語義上創建了聯繫。在default後直接拋出AssertionError錯誤,其含義就是「不要跑到這裏來」

8七、使用valueof前必須校驗

valueof先經過反射從枚舉類的常量聲明中查找,若找到就直接返回,若找不到則拋出無效參數異常。valueof本意是保護編碼中的枚舉安全性,使其不產生空枚舉對象。

8八、用枚舉實現工廠方法模式更簡潔

1)避免錯誤調用

2)性能更好

3)下降類間耦合

8九、枚舉項的數量限制在64個之內

爲了更好的使用枚舉,java提供了兩個枚舉集合EnumSet和EnumMap。EnumSet很好用,可是它有一個隱藏的特色。

 

 一句話總結:當枚舉項《64時,建立RegularenumSet實例,大於64時,建立JumboEnumSet實例對象。而JumboEnumSet內部分段處理。多了一次映射。因此小於64時效率比較高

90、當心使用註解

@inherited註解有利有弊,利的地方是一個註解只要標註到父類,全部的子類都會自動具備父類相同的註解,整齊、統一併且便於管理,弊的地方是單單

9一、枚舉和註解結合使用威力更大

9二、注意@override不一樣版本的區別

jdk1.5嚴格遵照重寫的定義。1.6之後開放了不少。好比說繼承接口的,在1.5不能用@override

第七章、泛型和反射

9三、java的泛型是類型擦除的

之因此這樣處理:

1)避免jvm的大換血。c++的泛型生命期延續到了運行期,而java是在編譯器擦除掉的。避免jvm大量的重構工做

2)版本兼容。在編譯器擦除能夠更好的支持原生類型。在java1.5以上,即便聲明一個list這樣的原生類型也是能夠正常編譯經過的,只是會產生警告

9四、不能初始化泛型參數和數組

T[] tArray=new T[3]; 編譯失敗

List<T> list=new ArrayList<T>();編譯成功

爲何數據不能夠。可是集合能夠?由於arraylist表面是泛型,其實已經在編譯器轉型爲object了。在某些狀況下,咱們確實須要泛型數組,能夠以下實現:

9五、強制聲明泛型的實際類型

9六、不一樣的場景使用不一樣的泛型通配符

?:任意類型

extends:某一個類的子類型

super:某一個類的父類型

1)泛型結構只參與‘讀’操做則限定上界

2)泛型結構只參與‘寫’操做則限定下界

9七、警戒泛型是不能協變和逆變的

協變:用一個窄類型替換寬類型

舉個例子:

逆變:一個寬類型替換窄類型

舉個例子:

逆變不屬於重寫,只是重載而已。因爲此時的dostuff方法已經與父類沒有任何關係類,只是子類獨立擴展出的一個行爲,因此是否聲明爲dostuff方法名意義不大,逆變已經不具備特別的意義類。因此重點關注下協變。(其實也就是多態)

泛型不支持協變、逆變

 9八、建議採用的順序是List<T> List<?> List<Object>

1)List<T>表示的是list集合中的元素都爲t類型,具體類型在運行期決定; List<?> 也是;List<Object>則表示集合中的全部元素

2)List<T>能夠進行讀寫操做,它的類型是固定的T類型,在編碼期不須要進行任何的轉型操做;List<?> 是隻讀類型的,由於編譯器不知道list中容納的是什麼類型的元素,並且讀出來的元素都是object類型的,須要主動轉型,因此它常常用於泛型方法的返回值。注意list<?>能夠remove,clear等,由於刪除動做與泛型類型無關 ; List<Object>也能夠讀寫操做,可是它執行寫入操做時須要轉型,而此時已經失去了泛型存在的意義了

9九、嚴格限定泛型類型採用多重界限

 舉個例子

100、數組的真實類型必須是泛型類型的子類型

10一、注意Class類的特殊性

java語言是先把java源文件編譯成後綴爲class的字節碼文件,而後再經過classloader機制把這些類文件加載到內存中,最後生成實例執行的。java使用一個元類MetaClass來描述加載到內存中的類數據,這就是Class類,它是一個描述類的類對象。特殊性:

1)無構造函數。Class對象是在加載類時由java虛擬機經過調用類加載器中的defineClas方法自動構造的

2)能夠描述基本類型。雖然8個基本類型在jvm中並非一個對象,它們通常存在於棧內,可是class類仍然能夠描述它們,int.class

3)其對象都是單例模式。一個Class的實例對象描述一個類,而且只描述一個類。

Class類是java的反射入口,只有在得到類一個類的描述對象後才能動態地加載,調用。通常得到一個class對象有三種途徑

1) 類屬性方式 String.class

2) 對象的getClass方法 new String().getClass()

3) forName方法加載 Class.forName("java.lang.String")

10二、實時選擇getDeclaredMethod和getMethod

getMethod:得到全部public訪問級別的方法,包括從父類繼承的方法

getDeclaredMethod:得到自身類的全部方法,包括public、private等

10三、反射訪問屬性或者方法時將accessible設置爲true

accessible屬性表示是否容易得到,是否須要進行安全檢查。咱們知道,動態修改一個類或方法都會受java安全體系的制約,而安全的處理是很是消耗資源的(性能很是低),所以對於運行期要執行的方法或屬性就提供類accessible可選項:由開發者決定是否要逃避安全體系的檢查

invoke的執行:

  @CallerSensitive
    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
        if (!override) {//保存了accessible的值
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
    }

accessible屬性只是用來判斷是否須要進行安全檢查的,若是不須要則直接執行,這就能夠大幅度地提高系統性能(因爲取消了安全檢查,也能夠運行private方法,訪問private屬性)。通過大量測試,在大量的反射狀況下,設置accessible爲true能夠提高性能20倍以上

10四、使用forName動態加載類文件

10五、動態加載不適合數組

數組是一個很是特殊的類,雖然它是一個類,但沒有定義類路徑。編譯後會爲不一樣的數組類型生成不一樣的類

因此其實是能夠動態加載一個對象數組的

可是這沒有任何意思,由於它不能生成一個數組對象,也就是說以上代碼只是把一個string類型的數組類和long類型的數組類加載到類內存中,並不能經過newinstance方法生成一個實例對象,由於它沒有定義數組的長度,沒有長度的數組是不容許存在的。

可是!能夠用使用array數組反射類來動態加載:

由於數組比較特殊,要想動態建立和反問數組,基本的反射是沒法實現的。因而java就專門定義來一個array數組反射工具類來實現動態探知數組的功能

10六、動態代理可使代理模式更加靈活

10七、動態代理可使裝飾者模式更加靈活

10八、不須要太多關注反射效率

通常狀況下反射並非性能的終極殺手,而代碼結構混亂、可讀性差則極可能會埋下隱患

第八章、異常

1十、提倡異常封裝

1)提升系統的友好性

2)提升系統的可維護性

3)解決java異常機制自身的缺陷,拋多個異常

1十一、採用異常鏈傳遞參數

public
class IOException extends Exception {
 public IOException() {
        super();
    }
   //記錄上一級異常
    public IOException(String message, Throwable cause) {
        super(message, cause);
    }
}

 1十二、受檢異常儘量轉化爲非受檢異常

1)受檢異常使接口聲明脆弱

oop要求咱們儘可能多的面向接口編程,能夠提升代碼的擴展性、穩定性等。可是一旦設計異常問題就不同了。好比一個接口拋出了異常a,隨着業務的發展,該接口可能還會拋出異常b、異常c等。這會產生兩個問題:

a)異常是主邏輯的補充邏輯,修改一個補充邏輯,就會致使主邏輯也被修改,也就是出現了實現類「逆影響」接口的情景,咱們知道實現類是不穩定的,而接口是穩定的,一旦定義了異常,則增長了接口的不穩定性

b)實現的類變動最終會影響到調用者,破壞了封裝性,這也是迪米特法則所不能容忍的(設計模式6原則:一個對象應該對其餘對象保持最少的瞭解)

2)受檢異常使代碼的可讀性下降

3)受檢異常增長了開發工做量

咱們知道,異常須要封裝和傳遞,只有封裝才能讓異常更容易理解,上層模塊才能更好的處理,可這也會致使低層級的異常沒完沒了的封裝,無故加劇了開發的工做量。可是咱們也不能把全部的受檢異常轉化爲非受檢異常,緣由是在編碼期上層模塊不知道下層模塊會拋出何種非受檢異常,只有經過規則或文檔來約束,能夠這樣說:

受檢異常:法律下的自由

非受檢異常:協約性質的自由

受檢異常威脅到系統的安全性、穩定性、可靠性、正確性時,不能轉換爲非受檢異常

11三、不要在finally塊中處理返回值

1)覆蓋了try代碼塊中的return返回值

在代碼中加上try代碼塊就標誌着運行時會有一個throwable線程監視着該方法的運行,若出現異常,則交由異常邏輯處理。

a)finally中修改基本數據類型返回值。返回值不會變化

方法在棧內存中運行,而且會按照‘先進後出’的原則執行,當dostuff方法執行完return a時,此方法的返回值已經肯定是int類型1,此後finally代碼塊再修改a的值已經於dostuff返回值沒有任何關係了

b)finally中修改基本引用類型返回值。返回值會變化

返回李四。person是一個引用對象,在try代碼塊中的返回值的person對象的地址。

2)屏蔽異常

異常線程在監視到有異常發生時,就會登記當前的異常類型爲dataformatexception,可是當執行finally代碼塊時,則會重新爲dostuff方法賦值,也就是告訴調用者‘該方法執行正確,沒有產生異常,返回值是1’

11四、不要在構造函數中拋出異常

1)加劇了上層代碼編寫者的負擔

只能經過文檔約束來告知上層代碼有異常

2)致使子類代碼膨脹

子類的無參構造函數默認調用的是父類的構造函數,因此子類的無參構造也必須拋出該異常或父類

3)違背來里氏替換原則(父類能出現的地方子類就能夠出現,並且將父類替換爲子類也不會產生任何異常)

      若是子類拋出的異常比父類拋出的異常範圍大,則沒法直接直接替換

4)子類構造函數擴展受限

  子類存在的緣由就是指望實現並擴展父類的邏輯,可是父類構造函數拋出異常卻會讓子類構造函數的靈活性大大下降

11五、使用throwable得到棧信息

aop編程能夠很輕鬆的控制一個方法調用哪些類,也能控制哪些方法容許被調用,通常來講切面編程只能控制到方法級別,不能實現代碼級別的植入,好比一個方法被類A調用時放回1,在類B調用時放回0,這就要求被調用者具備識別調用者的能力。在這種狀況下,可使用throwable得到棧信息,而後鑑別調用者並分別輸出

11六、異常只爲異常服務

不要包含業務流轉

11七、多使用異常,把性能問題放一邊

java的異常處理機制確實比較慢,單單從對象的建立來講,new一個ioexception會比string慢5倍,由於它要執行fillinstatcktrace方法,要記錄當前棧的快照,而string類則要直接申請一個內存建立對象。並且,異常類是不能緩存的,指望預先創建大量的異常對象是不可能的。(在jdk1.6,一個異常對象建立的時間1.4毫秒)

第九章、多線程和併發

11八、不推薦覆寫start方法

從多線程的設計思想來講。run方法是業務的處理邏輯,start是啓動一個線程,並執行run方法

11九、啓動線程前stop方法是不可靠的

stop():對於未啓動的線程(線程狀態爲new),會設置其標誌位爲不可啓動,而其餘的狀態則是直接中止

start():會先啓動線程,再判斷標誌位,若是標誌位是不可啓動,則中止線程

會有一個時間差

120、不使用stop方法中止線程

1)stop方法是過期的

2)stop方法會致使代碼邏輯不完整(好比說stop時還沒釋放io資源等等)

3)stop方法會破壞原子邏輯(會直接釋放全部鎖,致使原子邏輯受損)

12一、線程優先級只使用三個等級

線程的優先級(priority)決定了線程得到cpu運行的機會,優先級越高得到的運行機會越大,優先級越低得到的機會越小。但不保證順序執行。thread類中設置了三個優先級,建議使用優先常量,而不是1到10隨機的數字。

class thread{ 
/**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;
}

12二、使用線程異常處理器提高系統可靠性

jdk1.5之後,在thread類中增長了setUncaughtExceptionHandler方法,實現了線程異常的捕捉和處理

其實比較雞肋

12三、volatile不能保證數據同步

volatile關鍵字比較少用,緣由

1)java1.5以前該關鍵字在不一樣的操做系統上有不一樣的表現,所帶來的問題是移植性比較差;

2)只保證了可見性,不保證原子性

12四、異步運算考慮使用callable接口

1)儘量多地佔用系統資源,提供快速運算??

2)能夠監控線程執行的狀況,好比是否執行完畢,是否有返回值,是否有異常等

3)能夠爲用戶提供更好的支持,好比計算進度

12五、優先選擇線程池

12六、適時選擇不一樣的線程池來實現

12七、lock於synchronized是不同的

12八、預防線程死鎖

死鎖須要4個條件

1)互斥條件:一個資源每次只能被一個線程使用

2)資源獨佔條件:一個線程因請求資源而阻塞時,對已得到的資源保持不放

3)不剝奪條件:線程已得到的資源在未使用完以前,不能強行剝奪

4)循環等待條件:若干線程之間造成一種頭尾相接的循環等待資源關係

解決:

1)減小資源共享

2) 使用自旋鎖

lock.trylock(1,TimeUnit.SECONDS)必定時間內獲取不到鎖則放棄

12九、適當設置阻塞隊列長度

130、使用countdownlatch協調自線程

13一、cyclicbarrier讓多線程齊步走

功能與countdownlotch相似,增長了子線程結束後的處理線程

第十章、性能和效率

13二、提高java性能的基本方法

1)不要在循環條件中計算

2)儘量把變量、方法聲明爲fianl static類型

3)縮小變量的做用範圍(加快gc)

4)使用stringbuilder stringbuffer

5)使用非線性檢索

6)覆寫exception的fillinstacktrace方法

7)不創建冗餘對象

這個方法是用來記錄異常時的棧信息的,很是耗時,若是不關注能夠覆蓋之,會使性能提高10倍以上

13三、若非必要,不要克隆對象

經過clone方法生成一個對象時,就會再也不執行構造函數了,只是再內存中進行數據塊的拷貝,此方法看上去彷佛應該比new的性能好不少,可是java的締造者們也認識到二八原則,80%的對象是經過new關鍵字建立出來的,因此對new再生成對象時作了充分的性能優化,事實上,通常狀況下new生成的對象clone生成的性能方面要好不少

13四、推薦使用望聞問切的方式診斷性能

??????

13五、必須定義性能衡量標準

13六、槍打出頭鳥--解決首要系統性能問題

??????

13七、調整jvm參數以提高性能

13八、性能是個大‘咕咚’

?????

第11章、開源世界

13九、大膽採用開源工具

140、推薦使用guava擴展工具包

14二、apache擴展包

14三、推薦使用joda日期時間擴展包

14四、能夠選擇多種collections擴展

。。。。後面的就不說了 淡疼

相關文章
相關標籤/搜索