Effective Java部分讀書筆記

2.建立和銷燬對象

1.使用靜態工廠方法代替構造器

通常使用構造器(構造函數)建立對象實例,還可使用靜態工廠方法來建立對象實例。java

優勢

使用靜態工廠方法代替構造器建立對象實例有如下優勢:數組

1)靜態構造方法的名稱能夠是更加有意義的,具備更好的可讀性,而構造器的名稱必須與類名稱保持一致。緩存

2)靜態工廠方法沒必要在每次調用時都建立一個新對象,這對於那些須要重複建立相同對象的場景下尤爲有用。安全

3)靜態工廠方法能夠返回原返回類型的任意子類型對象性能優化

4)靜態工廠方法在建立參數化實例的時候,使程序更加簡潔。併發

缺點

1)使用靜態工廠方法建立實例的類,通常要求構造器是私有的,也就是說這個類不能被繼承,不能被子類實例化。ide

2)靜態工廠方法與其餘靜態方法並無實質的區別,不能顯而易見的判斷某靜態方法是否是工廠方法,因此,人們就作了一個約定,靜態工廠方法有一些慣用名稱:例如valueOf、of、getInstance、newInstance、getType、newType。函數

參考:1  2

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

若是類的構造器(或者靜態工廠方法)有多個參數,而且這些參數中有些是可選參數時,就要考慮使用Builder模式。工具

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

有些工具類不但願被實例化,由於實例化對它來講沒有什麼意思,例如java.lang.Math,java.util.Arrays。有人經過將類定義爲抽象類來避免類的實例化,不過,這樣容易引發誤解,讓用戶誤覺得這個類是專門爲了繼承而設計的。咱們能夠經過一個簡單的方法來避免類被實例化,就是顯式地將類的構造器聲明爲私有的(private),這樣就保證避免類的實例化(前提是類中的其餘成員不調用這個私有的構造器)。性能

4.避免建立沒必要要的對象

也就是說最好是可以重用已有的對象,而不是在每次須要的時候就建立一個相同功能的對象。

1)若是對象是不可變的(immutable),那它始終能夠被重用。基本類型的包裝類、String、BigInteger和BigDecimal都是不可變類。以字符串爲例:

String str=new String("Java");

這條語句執行時,每次都會建立一個新的String實例,不過這是沒必要要的,由於每次建立的實例是相同的,都是字符串"Java",而「Java」自己就是字符串實例。若是這種方法用在循環或者被頻繁調用的方法中,就會建立成千上萬個相同功能的實例。改進後的版本以下:

String str="Java"

這種方法並無建立新實例,而是使用已建立好的「Java」實例。而且,若是在代碼的其餘位置,使用了相同的字符串字面常量,仍然不會建立新對象,而是仍然複用這個「Java」實例,對於其餘的不可變類也是如此。

2)除了重用不可變類以外,還能夠重用那些建立了以後就不會修改的可變對象。參考

6.清除過時的對象引用(內存泄漏)

一個對象,若是在程序以後的執行過程當中不會再使用,正常狀況下,其所在的內存區域應該被回收,以便於存儲新的對象。不過,因爲一些緣由未被回收,仍是佔據着內存,致使可用內存量降低,就像內存變少了同樣,稱這種現象爲內存泄漏。Java雖然具備垃圾回收的功能,不過也會發生內存泄漏。

1)若是是類自身管理內存,可能會發生內存泄漏。以數組實現的棧爲例,棧獨自管理數組所佔的內存空間,代碼實現以下:

 

 1 public class Stack {
 2     private Object[] elements;
 3     private int size = 0;
 4     private static final int DEFAULT_INITIAL_CAPACITY = 100;
 5     public Stack() {
 6         elements = new Object[DEFAULT_INITIAL_CAPACITY];
 7     }
 8     public void push(Object e){
 9         ensureCapacity();
10         elements[size++] = e;
11     }
12     public Object pop(){
13         if(size == 0){
14             throw  new EmptyStackException();
15         }
16         return  elements[--size];
17     }
18     private void ensureCapacity(){
19         if(elements.length == size){
20             elements = Arrays.copyOf(elements, 2 * size + 1);
21         }
22     }
View Code

 

出棧時,pop()方法只是簡單的返回原棧頂元素的前一個元素,沒有作其餘任何的處理。假如,棧先增加到80,而後再收縮至20,以後的增加和收縮只是在20之內,那麼數組中[21,80]存儲的對以前對象的引用一直保持,儘管這些對象在程序中不在使用,垃圾收集器不會收集這些區域,這就形成了內存泄漏。解決辦法很簡單,在pop()方法里加一句代碼(第5行)便可:

1 public Object pop(){
2     if(size == 0){
3         throw  new EmptyStackException();
4     }
5     elementsp[size] = null;
6     return  elements[--size];
7 }

2)把對象引用放到緩存中,也常常會引發內存泄漏。

3)監視器和其餘回調也會引發內存泄漏

解決方法

能夠經過仔細檢查代碼或者藉助於Heap剖析工具(heap Profiler)發現內存泄漏問題。

7.避免使用終結方法

finalize()終結方法一般是不可預測的,應該避免使用。

 

3.對於全部對象都通用的方法

 

Object是Java中全部類的父類,它的equals、hashCode、toString、clone、finalize原則上應該被全部子類覆蓋,這些方法都有明確的通用約定,任何子類覆蓋這些方法的時候要遵循這些約定,不然就不能與其餘依賴這些約定的類一塊兒正常運做。

8.覆蓋equals時請遵照通用約定

不要覆蓋equlas的狀況

1)類的實例本質上都是惟一的,即每次建立的對象實例都是惟一的

2)不關心類是否提供了判斷「邏輯相等」的方法,也便是說類的使用過程當中不會出現判斷兩個類實例是否相等的狀況

3)父類已經實現了equals,子類利用這個equals能夠判斷自身實例是否「邏輯相等」。

覆蓋equals方法時的約定

1)自反性:對象等於其自身。對於任意非空的對象x,有x.equals(x)=true。

2)對稱性:對於任意非空x,y,若是x.equals(y)=true,那麼y.equals(x)也等於true。

3)傳遞性:對於任意非空x,y,z,若是x.equals(y)=true,y.equals(z)=true,那麼也有x.equals(z)=true。

4)一致性:若是兩個對象相等,只要兩個對象都未被修改,那麼它們任意時刻都應該是相等的。

5)非空性:全部的對象都不等於null。

實現高質量equals

1)使用「==」檢查參數「是否爲這個對象的引用」,這只是一種性能優化。

2)使用操做符"instanceof"檢查參數是否爲正確的類型。

3)把參數向下轉化(Object—>T)爲正確的類型。

4)最重要的,檢查參數中的各個域是否與對象中的各個區相匹配。若是域是除了double和float的基本類型,使用"=="進行比較。若是是對象引用域,遞歸調用equals。對於float類型,使用Float.compare方法,對於double,使用Double.compare,對float和Double進行特殊處理,是由於存在着Float.NaN、-0.0f以及相似的double常量。若是域是數組,將以上原則應用到每一個元素上。

最後,覆蓋equlas()是總要同時覆蓋hashCode。不要講equals中的Object類型替換爲其餘類型,由於這樣只是重載了,而非覆蓋。可使用@Override關鍵字顯示指出覆蓋,供編譯器檢查,防止誤把覆蓋寫成重載。

9.覆蓋equals時總要覆蓋hashCode

hashCode約定

1)在程序的執行期間,若是equals的比較操做所用到的信息沒有被修改,也就是說對象仍是那個對象,那麼屢次調用hashCode應該始終如一的返回同一個整數。不過,程序屢次執行時,能夠返回不一樣的整數。

2)經過equals比較相等的兩個對象,調用hashCode應該返回相同的整數(哈希碼)。

3)經過equals比較不相等的兩個對象,調用hashCode不必定要返回不相等的整數。不過,爲了減小哈希衝突,不相等的對象調用其實應該返回不一樣的整數。

不覆蓋hashCode會怎麼樣

若是覆蓋了equals的實現,未覆蓋hashCode的話,會影響這個類與基於哈希的集合一塊兒正常運做,例如HashMap,HashSet以及Hashtable。由於沒有覆蓋的話,會調用Object的hashCode實現,即便子類調用已重寫的equals,獲得兩個相等的對象,不過經過Object的hashCode計算得出的哈希碼卻並不相同(本該相同),所以不能與基於哈希的集合一塊兒運做。

10.始終要覆蓋toString

Object提供的toString的輸出包含類的名稱,一個「@」已經標識對象的無符號十六進制散列碼。例如,「Phone@163b91」。當對象被傳遞給println、printf字符串鏈接符"+"時,自動調用對象的toString()方法。應該重寫toString,使其返回對本類有意義的信息。

4.類和接口

15.使可變性最小化

實例對象不能被修改的類成爲不可變類。不可變類的對象實例包含的全部信息必須在建立時提供,並在對象的生命週期內保持不變。

如何實現不可變類

1)將類的全部字段聲明爲private類型,將字段定義爲final類型,使字段在第一次初始化以後就不能再被修改。將類聲明爲final類型,表示不能被繼承,防止子類無心識修改對象狀態。

2)不提供任何修改對象字段的方法,也就是不提供任何改變對象狀態的方法。

優勢

1)不可變類易於設計、實現和使用,不容易出錯且更加安全,不變的東西更容易控制。

2)不可變對象是線程安全的,由於即便是多個線程併發訪問這類對象,它們也不會遭到破壞,由於並未提供更改對象狀態的方法。

缺點

對於每一個不一樣的值,不可變類都要提供一個單獨的對象。這對於大型的對象來講,低價很高。例如上百萬位的BigInteger。 

7.方法

38.檢查參數的有效性

對於包含參數的方法,在使用以前應該檢查其參數的有效性。若是參數是索引、長度等要保證是非負數,若是是對象的引用,要保證是非空的。可是,並非任什麼時候候都要檢查參數的有效性,若是參數的有效性檢查是昂貴的,甚至是不切實際的,而且在方法的計算過程當中已經隱含了對參數的有效性檢查,此時沒必要檢查參數的有效性。若是參數無效,能夠採起拋出異常的方式,以下:

public static double getGravity(double mass) {  
    // 參數有效性檢查  
    if (mass <= 0) {  
        throw new IllegalArgumentException("無效質量:" + mass);  
    }  
  
    return mass * 9.82;  
}  

41.慎用重載

8.通用程序設計

48.若是須要精確的答案,避免使用float和double。

Java中的float和double在進行算術運算時,不保證獲得徹底精確的結果。例如

System.out.println(1-0.9)

的結果可能爲0.100000001。

可使用BigDecimal解決須要精確答案的問題。

52.經過接口引用對象

若是能夠的話,優先使用接口引用對象,而不是使用類。

何時使用

若是類是實現了某個接口,也就是說類是有接口的話,優先考慮使用接口做爲類型。不過,要意識到使用接口類型的對象,並不能訪問類中新增的方法(相對接口來講)。

有什麼好處

使用接口做爲類型能夠增長程序的靈活性,很容易實現接口實現類間的替換。聲明Vector的兩種方法:

//方法1
Vector<Subscriber> subscribers = new Vector<Subscriber>();
//方法2
List<Subscriber> subscribers = new Vector<Subscriber>();

若是須要更改接口的實現,將Vector改成ArrayList(Vector和ArrayList都是List接口的實現),若是程序中使用方法2的話,只須要將右側的Vector改成ArrayList便可。

爲何要改變實現

新的實現可能會提供更好的性能。

相關文章
相關標籤/搜索