Effective Java讀後感

Effective Java》讀後感java

1       建立和銷燬對象

1.1    考慮用靜態工廠方法代替構造器

靜態工廠方法優勢:程序員

  • 靜態工廠方法與構造器(構造方法)不一樣的第一大優點在於,它們有名稱。見名知意,突出區別。編程

  • 靜態工廠方法與構造器不一樣的第二大優點在於,沒必要在每次調用它們的時候都建立一個新對象。數組

  • 靜態工廠方法與構造器不一樣的第三大優點在於,它們能夠返回原返回類型的任何子類型的對象。緩存

  • 靜態工廠方法與構造器不一樣的第四大優點在於,在建立參數化類型實例的時候,它們使代碼變得更加簡潔。安全

例如:Map<String,List<String>> map=newHashMap<String,List<String>>();併發

假設HashMap提供了靜態工廠方法:(目前是沒有該靜態方法)框架

public static <K,V> HashMap<K,V> newInstance(){dom

         return newHashMap<K,V>();ide

}

上面的代碼就能夠簡寫爲:Map<String,List<String>> map= HashMap.newInstance();

靜態工廠方法缺點:

主要缺點在於,類若是不含公有的或者受保護的構造器,就不能被子類化;

第二個缺點在於,它們與其餘的靜態方法實際上沒有任何區別。

1.2    遇到多個構造器參數時要考慮用構建器

假如咱們的一個實體類有不少的屬性值,可是這些屬性值又是可選的。若是咱們遇到這樣的是類,如何設計出方便的實體類呢?

 

  • 一般解決辦法一:重疊構造器

 

複製代碼

 

public class User {

 

   private String id; // id(必填)

 

   private String name; // 用戶名(必填)

 

   private String email; // 郵箱(可選)

 

   private int age; // 年齡(可選)

 

   private String phoneNumber; // 電話(可選)

 

   private String address; // 地址(可選)

 

   public User(String id, String name) {

       this(id, name, "qq.com", 0, "120", "廣州");

    }

 

   public User(String id, String name, String email) {

       this(id, name, email, 0, "120", "廣州");

    }

 

   public User(String id, String name, String email, int age) {

       this(id, name, email, age, "120", "廣州");

    }

 

   public User(String id, String name, String email, int age, StringphoneNumber) {

       this(id, name, email, age, phoneNumber, "廣州");

    }

 

   public User(String id, String name, String email, int age, StringphoneNumber, String address) {

       this.id = id;

       this.name = name;

       this.email = email;

       this.age = age;

       this.phoneNumber = phoneNumber;

       this.address = address;

    }

 

   public String getId() {

       return id;

    }

 

   public String getName() {

       return name;

    }

 

   public String getEmail() {

       return email;

    }

 

   public int getAge() {

       return age;

    }

 

   public String getPhoneNumber() {

       return phoneNumber;

    }

 

   public String getAddress() {

       return address;

    }

}

 

注:許多你不想設置的參數,可是還不得不爲他們傳遞值

 

  • 一般解決辦法二: JavaBean模式

調用一個無參構造器來創造對象,而後調用setter方法來設置每一個必須的參數,以及每一個相關的可選參數

 

 

複製代碼

 

public class User {

 

   private String id; // id(必填)

 

   private String name; // 用戶名(必填)

 

   private String email; // 郵箱(可選)

 

   private int age; // 年齡(可選)

 

   private String phoneNumber; // 電話(可選)

 

   private String address; // 地址(可選)publicUser() {

       super();

    }

 

   public void setId(String id) {

       this.id = id;

    }

 

   public void setName(String name) {

       this.name = name;

    }

 

   public void setEmail(String email) {

       this.email = email;

    }

 

   public void setAge(int age) {

       this.age = age;

    }

 

   public void setPhoneNumber(String phoneNumber) {

       this.phoneNumber = phoneNumber;

    }

 

   public void setAddress(String address) {

       this.address = address;

    }

 

   public String getId() {

       return id;

    }

 

   public String getName() {

       return name;

    }

 

   public String getEmail() {

       return email;

    }

 

   public int getAge() {

       return age;

    }

 

   public String getPhoneNumber() {

       return phoneNumber;

    }

 

   public String getAddress() {

       return address;

    }

 

   @Override

   public String toString() {

       return "User [id=" + id + ", name=" + name + ",email=" + email + ", age=" + age + ", phoneNumber="

                + phoneNumber + ",address=" + address + "]";

    }

}

 

注:JavaBeans模式自身有着很嚴重的缺點。由於構造過程被分到幾個調用中,在構造過程當中JavaBean可能處於非一致的狀態。JavaBeans模式阻止了把類作成不可變的可能,這就須要確保他的線程安全。

 

 

  • 解決辦法三:構建器

 

 

複製代碼

 

public class User {

 

   private String id;                // id(必填)

 

   private String name;            // 用戶名(必填)

 

   private String email;            // 郵箱(可選)

 

   private int age;                // 年齡(可選)

 

   private String phoneNumber;     //電話(可選)

 

   private String address;         //地址(可選)

 

 

   public static class Builder{

       

       private String id;                // id(必填)

 

       private String name;            // 用戶名(必填)

 

       private String email;            // 郵箱(可選)

 

       private int age;                // 年齡(可選)

 

       private String phoneNumber;     //電話(可選)

 

       private String address;         //地址(可選)

 

       public Builder(String id, String name) {

           super();

           this.id = id;

           this.name = name;

       }

       

       public Builder email(String email){

           this.email = email;

           return this;

       }

       public Builder age(int age){

           this.age = age;

           return this;

       }

       public Builder phoneNumber(String phoneNumber){

           this.phoneNumber = phoneNumber;

           return this;

       }

       

       public Builder address(String address){

           this.address = address;

           return this;

       }

       public User builder(){

           return new User(this);

       }

    }

   private User(Builder builder){

       this.id = builder.id;

       this.name = builder.name;

       this.email = builder.email;

       this.age = builder.age;

       this.phoneNumber = builder.phoneNumber;

       this.address = builder.address;

    }

   

   @Override

   public String toString() {

       return "User [id=" + id + ", name=" + name + ",email=" + email + ", age=" + age + ", phoneNumber="

                + phoneNumber + ",address=" + address + "]";

    }

 

   public String getId() {

       return id;

    }

 

   public String getName() {

       return name;

    }

 

   public String getEmail() {

       return email;

    }

 

   public int getAge() {

       return age;

    }

 

   public String getPhoneNumber() {

       return phoneNumber;

    }

 

   public String getAddress() {

       return address;

    }

}

 

 注:不直接生成想要的對象,而是讓客戶端利用全部必要的參數調用構造器(或者靜態工廠),獲得一個builder對象。而後客戶端在builder對象上調用相似於setter的方法,來設置每一個相關的可選參數。最後,客戶端調用無參的build方法來生成不可變的對象。

 

測試代碼

 

   public static void main( String[] args )

    {

       User user = new User.Builder(UUID.randomUUID().toString(),"parry").address("廣州").builder();

       System.out.println(user.toString());

    }

1.3    用私有構造器或者枚舉類型強化Singleton屬性

1.4    經過私有構造器強化不可實例化的能力

1.5    避免建立沒必要要的對象

  • 例如:Strings=new String(「abc」); // Don’t do this

該語句每次被執行的時候都建立一個新的String實例,可是這些建立對象的動做全都是沒必要要的。傳遞給String構造器的參數(」abc」),自己就是一個String實例,功能方面等同於構造器建立的全部對象。若是這個方法在一個循環中,就會建立成千上萬的沒必要要的String實例。

正確的寫法:String s=」abc」;

  • Java1.5版本的新特性中,有一種建立多與對象的新方法:自動裝箱!

例如:

Long sum=0L;

for(long i=0; i<Integer.MAX_VALUE; i++){

         sum+= i;

}

system.out.println(sum);

執行以上代碼,耗時大約43秒;

這段程序算出的答案是正確的,可是比實際狀況要更慢一些,只由於變量sum使用的Long類型,而不是long,這意味着程序構造了大約231次方個多餘的Long實例。若是將Long改成基本類型long。程序運行時長爲6.8秒附近。

結論很明顯:優先使用基本類型而不是裝箱基本類型,要小心無心識的自動裝箱。

1.6    消除過時的對象引用

內存泄漏:若是一個棧先是增加,而後再收縮,那麼從棧中彈出來的對象將不會被當作垃圾回收,即便使用棧的程序再也不引用這些對象,它們也不會被回收。由於,棧內部維護着對這些對象的過時引用(obsolete reference)。所謂的過時引用,是指永遠也不會再被解除引用。

出現內存泄漏的三種須要注意的狀況:

一:類是本身管理內存;

二:緩存; 把對象放入緩存中,很容易被遺忘。

三:監聽器和其餘回調。

1.7    避免使用終結方法

2       對於全部對象都通用的方法

儘管Object是一個具體類,可是設計它主要是爲了擴展。它的全部非final方法(equalshashCodetoStringclonefinalize)都用明確的通用約定,由於它們被設計成要被覆蓋(override)的。

2.1    覆蓋equals時請遵照通用約定

equals方法實現了等價關係:

  • 自反性(reflexive:對於任何非null的引用值xx.equals(x)必須返回true

  • 對稱性(symmetric:對於任何非null的引用值xy,當且僅當y.equals(x)返回true時,x.equals(y)必須返回true

  • 傳遞性(transitive:對於任何非null的引用值xyz,若是x.equals(y)返回true,y.equals(z)返回true,那麼x.equals(z)必須返回true

  • 一致性(consistent:對於任何非null的引用值xy,只要equals的比較操做再對象中全部的信息沒有被修改,屢次調用x.equals(y)就會一致地返回true,或者一致的返回false

  • 非空性(Non-nullity):對於任何非null的引用值xx.equals(null)必須返回false

  •     覆蓋equals時總要覆蓋hashCode

引用java API,關於ObjecthashCode方法的一段話:

返回該對象的哈希碼值。支持此方法是爲了提升哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。

hashCode 的常規協定是:

 

  • Java 應用程序執行期間,在對同一對象屢次調用 hashCode 方法時,必須一致地返回相同的整數,前提是將對象進行 equals 比較時所用的信息沒有被修改。從某一應用程序的一次執行到同一應用程序的另外一次執行,該整數無需保持一致。

  • 若是根據equals(Object) 方法,兩個對象是相等的,那麼對這兩個對象中的每一個對象調用 hashCode 方法都必須生成相同的整數結果。

  • 若是根據equals(java.lang.Object) 方法,兩個對象不相等,那麼對這兩個對象中的任一對象上調用 hashCode 方法不要求必定生成不一樣的整數結果。可是,程序員應該意識到,爲不相等的對象生成不一樣整數結果能夠提升哈希表的性能。

  •     始終要覆蓋toString

  •     謹慎覆蓋clone

  •     考慮實現Comparable接口

3       類和接口

3.1    使類和成員的可訪問性最小化

對於成員(域、方法、嵌套類和嵌套接口)有四種可訪問級別:下面根據可訪問性遞增順序說明:

  • 私有的(private:只有在聲明該成員的頂層類內部才能夠訪問這個成員;

  • 包級私有(package-private):聲明該成員的包內部的任何類均可以訪問這個成員。從技術上講,它被稱爲「缺省訪問級別」;

  • 受保護的(protected):聲明該成員的類的子類能夠訪問這個成員(可是有一些限制[JLS6.6.2])。

  • 公有的(public:在任何地方均可以訪問。

  •     在公有類中使用訪問方法而非公有域

  •     使可變性最小化

爲了使類稱爲不可變,要遵循下面五條規則:

1,  不要提供任何會修改對象狀態的方法;

2,  保證類不會被擴展;

3,  使全部的域都是final的;

4,  使全部的域都稱爲私有的;

5,  確保對於任何可變組件的互斥訪問

3.4    複合優先於繼承

3.5    要麼爲繼承而設計,提供說明文檔,要麼就禁止繼承

3.6    接口優於抽象類

現有的類能夠很容易被更新,以實現新的接口;

接口是定義mixin(混合類型)的理想選擇;

接口容許咱們構造非層次結構的類型框架。

3.7    接口只用於定義類型

3.8    類層次優於標籤類

3.9    用函數對象表示策略

例如:比較器函數表明一種爲元素排序的策略。

3.10        優先考慮靜態成員類

4       泛型

4.1    請不要在新代碼中使用原生態類型

4.2    消除非受檢警告

4.3    列表優先於數組

4.4    優先考慮泛型和泛型方法

4.5    利用有限制通配符來提高API的靈活性

4.6    優先考慮類型安全的異構容器

5       枚舉和註解

5.1    enum代替int常量

5.2    用實例域代替序數

5.3    EnumSet代替位域

5.4    EnumMap代替序數索引

5.5    註解優先於命名模式

5.6    用標記接口定義類型

6       方法

6.1    檢查參數的有效性

6.2    必要時進行保護性拷貝

6.3    謹慎設計方法簽名

謹慎地選擇方法的名稱;見名知意

不要過於追求提供便利的方法;

避免過長的參數列表;

6.4    慎用重載

6.5    慎用可變參數

6.6    返回零長度的數組或者集合,而不是null

6.7    爲全部導出的API元素編寫文檔註釋

7       通用程序設計

7.1    將局部變量的做用域最小化

7.2    for-each循環優先於for循環

沒法使用for-each循環的三種狀況:

1,過濾:若是須要遍歷集合,並刪除選定的元素,就須要使用顯式迭代器,以即可以調用它的remove方法;

2,轉換:若是須要遍歷列表或者數組,並取代它部分或者所有的元素值,就須要列表迭代器或者數組索引,以便設定元素的值

3,平行迭代:若是須要並行的遍歷多個集合,須要顯式的控制迭代器或者索引變量,以便全部迭代器或者索引變量均可以獲得同步前移

以上狀況,須要使用普通for循環。

7.3    瞭解和使用類庫

7.4    精確的答案,避免使用floatdouble

精確計算,推薦使用BigDecimal(性能會相對慢一些)

7.5    基本類型優先於裝箱基本類型

7.6    若是使用其餘類型更合適,避免使用string類型

字符串不適合代替其餘的值類型;

字符串不適合代替枚舉類型;

字符串不適合代替彙集類型;

字符串不適合代替能力表;

7.7    小心字符串鏈接的性能

爲了得到能夠接收的性能,請使用StringBuilderStringBuffer代替String進行字符串的拼接。

7.8    經過接口引用對象

7.9    接口優先於反射機制

使用反射機制的缺點:

1,喪失了編譯時類型檢查的好處,包括異常檢查;

2,執行反射訪問所須要的代碼很是笨拙和冗長。

2,性能損失。反射方法調用比普通方法調用慢了不少。

7.10        謹慎的調用本地方法

7.11        謹慎的進行優化

不要去計較效率上的一些小小的得失,在97%的狀況下,不成熟的優化纔是一切問題的根源。

 

在優化方面,咱們應該遵照兩條規則:

1,  不要進行優化;

2,  仍是不要進行優化;也就是說在你沒有絕對清晰的優化方案以前,請不要進行優化。

 

總結:不要費力編寫快速的程序,應該努力編寫好的程序,速度天然隨之而來。

7.12        遵照廣泛接受的命名慣例

8       異常

8.1    只針對異常的狀況才使用異常

8.2    對可恢復的狀況使用受檢異常,對編程錯誤使用運行時異常

8.3    避免沒必要要的使用受檢異常

8.4    優先使用標準的異常

8.5    拋出與抽象相對應的異常

8.6    每一個方法拋出的異常都要有文檔或註釋

8.7    在細節消息中包含能捕獲失敗的信息

8.8    努力讓失敗保持原子性

8.9    不要忽略異常

9       併發

9.1    同步訪問共享的可變數據

9.2    避免過分同步

9.3    executortask優先於線程

9.4    併發工具優先於waitnotify

9.5    線程安全性的文檔化

9.6    慎用延遲初始化

9.7    不要依賴線程調度器

9.8    避免使用線程組

10  序列化

10.1        謹慎實現Serializable接口

缺點是:

1,  類實現該接口以後,一旦一個類被髮布,就大大下降了「改變該類的實現」的靈活性。

2,  它增長了出現bug和安全漏銅的可能性;

3,隨着類發行新的版本,相關的測試負擔也增長了。

10.2        考慮使用自定義的序列化形式

10.3        保護性地編寫readObject方法

Mutableperiod***。

編寫更加健壯的readObject方法:

  • 對於對象引用域必須保持爲私有的類,要保護性的拷貝這些域中的每一個對象。不可變類的可變組件就屬於這一類別;

  • 對於任何約束條件,若是檢查失敗,則拋出一個InvalidObjectException異常。這些檢查動做應該跟在全部的保護性拷貝以後。

  • 若是整個對象圖在被反序列化以後必須進行驗證,就應該使用ObjectInputValidation接口

  • 不管是直接仍是間接方式,都不用調用類中任何可被覆蓋的方法

  •         對於實例控制,枚舉類型優先於readResolve

若是依賴readResolve進行實例控制,帶有對象引用類型的全部實例域則都必須聲明爲tansient的。不然,會被Mutableperiod***。

10.5        考慮用序列化代理代替序列化實例

相關文章
相關標籤/搜索