Effective Java 讀書筆記

Effective Java-對於所有對象都通用的方法


#表示需深入理解
一、覆蓋equals時請遵守通用約定
  覆蓋equals方法看起來似乎很簡單,但是有許多覆蓋方式會導致錯誤,並且後果非常嚴重。最容易避免這類問題的方法就是不覆蓋equals方法,在這種情況下,。類的每個實例只與自身相等。如果滿足了以下任何一個條件,這就是所期望的結果:
  (1)類的每個實例本質上都是唯一的。
  (2)不關心類是否提供了“邏輯相等”的測試功能。
  (3)超類已經覆蓋了equals,從超類繼承過來的行爲對於子類也是合適的。
  (4)類是私有的或包級私有的,可以確定它的equals方法永遠不會被調用。
  那麼,什麼時候應該覆蓋Object.equals呢?如果類有自己特有的“邏輯相等”概念(不同於對象等同的概念),而且超類還沒有覆蓋equals以實現期望的行爲,這是我們需要覆蓋equals。通常情況下,這屬於“值類”的情形。有一種“值類”不需要覆蓋equals方法,即用實例受控確保“每個值至多隻存在一個對象”的類。例如枚舉。對於這樣的類而言,邏輯相同和對象等同是一回事。
  在覆蓋equals方法時,你必須要遵守它的通用約定:自反性,對稱性,傳遞性,一致性,非空性。
  違反傳遞性,主要是因爲面嚮對象語言中關於等價關係的一個基本問題。我們無法在擴展可實例化的類的同事,既增加新的組件,同時有保留equals約定。
  結合以上所有的要求,得出了一下實現高質量equals方法的訣竅:
  1、使用==操作符檢查“參數是否爲這個對象的引用”。
  2、使用instance of 操作符檢查“參數是否爲正確的類型”。所謂“正確的類型”一般指equals方法所在的那個類。有些情況下,是指該類所實現的接口。
  3、把參數轉換成正確的類型。
  4、對於該類中的每個“關鍵”域,檢查參數中的域是否與該對象中對應的域相匹配。對於既不是float也不是douvle類型的基本類型域,可以使用==操作符進行比較;對於對象引用域,可以遞歸地調用equals方法;對於float或double域,可以使用Fload.compare或Double.compare方法。域的比較順序可能會影響到equal方法的性能。應該先比較最有可能不一致的域,或者是開銷最低的域。
  5、當你編寫完成了equals方法之後,應該檢查它是否是對稱的、傳遞的、一致的?
  最後是一些告誡:覆蓋equals時總要覆蓋hashCode;不要企圖讓equals方法過於智能;不要講equals聲明中的Object對象替換爲其它類型。

二、覆蓋equals時總要覆蓋hashCode
  

三、始終要覆蓋toString
  

四*、謹慎地覆蓋clone
  

五、考慮實現Comparable接口
  

Effective Java-類和接口


一、使類和成員的可訪問性最小化
  區別設計良好的模塊與設計不好的模塊的一個最重要因素在於,這個模塊對於外部而言,是否隱藏了其內部數據和其他實現細節。
  信息隱藏可以有效的解除組成系統的各模塊之間的耦合關係。使得這些模塊可以獨立地開發、測試、理解和優化。
  信息隱藏的第一規則是,儘可能地使每個類或者成員不被外界訪問。就是使用儘可能最小的訪問級別。
  實例域決不能是公有的。如果域是非fianl的,或者一個指向可變對象的final引用,那麼一旦這個域成爲公有的,就放棄了對存儲在這個域中的值進行限制的能力。同樣的建議也使用於靜態域。
  另外,長度非零的數組總是可變的。類具有公有的靜態final數組域,或者返回數組域的訪問方法,這幾乎總是錯誤的。

二、使公有類中使用訪問方法而非公有域
  堅持面對對象程序設計思想的看法:如果類可以在它所在包的外部進行訪問,就提供訪問方法。然而,如果類是包級私有的,或者是私有的嵌套類,直接暴露它的數據域並沒有本質的錯誤。

三、使可變性最小化
  不可變類只是其實例不能被修改的類。每個實例中包含的所有信息都必須在創建的時候就提供,並在對象的整個生命週期內固定不變。不可變類比可變類更加易於設計、實現和使用,它們不容易出錯,且更加安全。
  爲了使類成爲不可變,要遵循下面五條規則:1、不要提供任何會修改對象狀態的方法。2、保證類不會被擴展。3、使所有的域都是final的。4、使所有的域都成爲私有的。5、確保對於任何可變組件的互斥訪問。(如果類有指向可變對象的域,則必須保證客戶端無法獲得指向這些對象的引用;並且,永遠不要用客戶端提供的對象引用來初始化這樣的域)。
  不可變對象本質上是線性安全的,它們不要求同步。可以被自由地共享。不可變對象應該儘可能地重用現有的實例。對於頻繁使用的值,爲它們提供靜態final常量。
  不可變類真正唯一的缺點是,對於每個不同的值都需要一個單獨的對象

四、複合優先於繼承
  繼承是實現代碼重用的有力手段,但它並非永遠是完成這項工作的最佳工具。使用不當會使軟件變得很催乳奧。在包的內部使用繼承是非常安全的,在那裏,子類和超類的實現都處在同一個程序員的控制之下。對於專門爲繼承而設計,並且具有很好的文檔說明的類來說,使用繼承也是非常安全的。然而,對普通的具體類進行跨越包邊界的繼承,則是非常危險的。
  與方法調用不同,繼承打破了封裝。子類依賴於其超類中特定功能的實現細節。超類的實現一旦變化,子類有可能會被破壞。在擴展一個類的時候,可能由於覆蓋舊方法或未覆蓋新方法而導致子類的脆弱。
  有一種方法可以避免前面的問題,便是使用“複合”。在新類中增加一個私有域,它引用現有類的一個實例。新類中的每個實例方法都可以調用被包含的現有類實例中的方法,這被稱爲轉發。複合和轉發的結合,從技術的角度而言並不是委託,除非包裝對象把自身傳遞給被包裝的對象。
  只有當子類和超類之間確實存在子類型關係時,使用繼承纔是恰當的。

五、要麼爲繼承而設計,並提供文檔說明,要麼就禁止繼承。
  對於爲了繼承而設計的類,唯一的測試方法就是編寫子類。允許繼承的類中,構造器決不能調用可被覆蓋的方法。

六#、接口優於抽象類
  

七、接口只用於定義類型
  

八、類層次優於標籤類
  

九、用函數對象表示策略
  函數指針的主要用途就是實現策略模式。爲了在Java中實現這種模式,要申明一個接口來表示該策略,並且爲每個具體策略聲明一個實現了該接口的類。當一個具體策略值被使用一次時,通常使用匿名類來聲明和實例化這個具體策略類。當一個具體策略是設計用來反覆使用的時候,它的類通常就要被實現 私有的靜態成員類,並通過公有的靜態final域被導出,其類型爲該策略接口。

十、優先考慮靜態成員類
  如果聲明成員類不要求訪問外圍實例,就要始終把static修飾符放在它的聲明中。

Effective Java-泛型


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

二、消除非受檢警告
  要儘可能地消除每一個非受檢警告。如果無法消除警告,同時可以證明引起警告的代碼是安全的,可以用@SuppressWarnings(”unchecked”)註解來禁止這條警告。每當使用SuppressWarnings(“unchecked”)註解時,都要調價一條註釋,說明爲什麼這麼做是安全的。

三、列表優先於數組
  數組與泛型相比,有兩個重要的不同點。首先,數組是協變的,泛型則是不可變的。其次,數組是具體的,只有運行時才知道並檢查它們的元素類型約束,而泛型是通過擦除實現的,只是在編譯是強化它們的類型信息。從技術的角度來說,像E,List和List這樣的類型被稱爲不可具體化的類型。不可具體化類型(non-reifiable)是指其運行時包含的信息比它編譯是包含的信息更少的類型。

四、優先考慮泛型
  

五、優先考慮泛型方法
  

六、利用有限制的通配符類型來提升API的靈活性
  

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

Effective Java-枚舉和註解


一、用enum代替int常量
  爲了將數據與枚舉常量關聯起來,得聲明實例域,並編寫一個帶有數據並將數據保存在域中的構造器。
  java枚舉類型背後的基本想法非常簡單:它們就是通過公有的靜態final域爲每個枚舉常量導出實例的類。因爲沒有可訪問的構造器,枚舉類型是真正的final。枚舉將數據與枚舉常量關聯起來,它允許添加任意的方法和域,並實現任意的接口。如果要將不同的行爲和每一個枚舉常量相關聯起來,可以定義一個抽象方法。通過策略枚舉,可以爲每一個枚舉常量定義不同的策略,強制爲每一個枚舉常量選擇一個策略枚舉,將行爲委託給策略枚舉。

二、用實例域代替序數
  永遠不要根據枚舉的序數導出與它關聯的值,而是要將它保存在一個實例域中。

三、用EnumSet代替位域
  

四、用EnumMap代替序數索引
  

五、用接口模擬可伸縮的枚舉
  雖然無法編寫可擴展的枚舉類型,卻可以通過編寫接口以及實現該接口的基礎枚舉類型,對它進行模擬。

六、堅持使用Override註解
  

七、用標記接口定義類型
  標記接口有兩點勝過標記註解。首先,也是最重要的一點是,標記接口定義的類型是由被標記類的實例實現的;標記註解則沒有定義這樣的類型。另一個優點是:它們可以被更加精確地進行鎖定。
  標記註解勝過標記接口的最大優點在於,它可以通過默認的方式添加一個或者多個註解類型元素,給已被使用的註解類型添加更多的信息。
  那麼什麼時候該使用標記註解,什麼時候該使用標記接口呢?很顯然,如果標記是應用到任何程序元素而不是類或者接口,就必須使用註解。如果標記只用於類和接口,要先確定:我要編寫一個還是多個只接受這種標記的方法呢?如果是這種情況,優先使用標記接口。這樣可以提供編譯時進行類型檢查的好處。

Effective Java-方法


一、檢查參數的有效性
  絕大多數方法和構造器對於傳遞給它們的參數值都會有某些限制。你應該在文檔中清楚地指明所有這些限制,並且在方法體的開頭處檢查參數。這一規則也有例外,一個很重要的例外是,在有些情況下,有效性檢查工作非常昂貴,或者根本是不切實際的,而且有效性檢查已隱含在計算過程中完成。然而,不加選擇地使用這種方法會導致失去失敗原子性。
  不要從本條目的內容中得出這樣的結論:對參數的任何限制都是好事。相反,在設計方法時,應該使它們儘可能地通用,並符合實際的需要。

二、必要時進行保護性拷貝
  假設類的客戶端會盡其可能來破壞這個類的約束條件,因此你必須保護性地設計程序。保護性拷貝是在檢查參數的有效性之前進行的,並且有效性檢查是針對拷貝之後的對象,而不是針對原始對象。對於參數類型可以被不可信任方子類化的參數,請不要使用clone方法進行保護性拷貝。

三、謹慎設計方法簽名
  謹慎地選擇方法的名稱;不要過於追求提供便利的方法;避免過長的參數列表(目標是四個參數,或者更少。對於參數類型,要優先使用接口而不是類。對於boolean參數,要優先使用兩個元素的枚舉類型)

四、慎用重載
  要調用哪個重載方法是在編譯是做出決定的。比如對於編譯時類型相同的:Collection

Effective Java-通用程序設計


一、將局部變量的作用域最小化
  要使局部變量的作用域最小化,最有力的方法就是在第一次使用它的地方聲明。幾乎每個局部變量的聲明都應該包含一個初始化表達式。

二、for-each循環優先於傳統的for循環
  

三、瞭解和使用類庫
  

四、如果需要精確的答案,請避免使用float和double
  

五、基本類型優先於裝箱基本類型
  

六、如果其他類型更適合,則儘量避免使用字符串
  字符串不適合代替其他的值類型;字符串不適合代替枚舉類型;字符串不適合代替聚集類型;字符串也不適合代替能力表。

七、當心字符串連接的性能
  

八、通過接口引用對象
  

九、接口優先於反射機制
  

十、謹慎地使用本地方法
  

十一、謹慎地進行優化
  

十二、遵守普遍接受的命名習慣
  

Effective Java-異常


一、只針對異常的情況才使用異常
  

二、對可恢復的情況使用受檢異常,對編程錯誤使用運行時異常
  如果期望調用者能夠適當地恢復,對於這種情況就應該使用受檢的異常。用運行時異常來表示編程錯誤。大多數的運行時異常都表示前提違例。

三、避免不必要地使用受檢的異常
  

四、優先使用標準異常
  

五、拋出與抽象相對應的異常
  如果不能阻止或者處理來自更低層的異常,一般的做法是使用異常轉譯,除非低層方法碰巧可以保證它拋出的異常對高層也適合才能將異常從低層傳播到高層。異常鏈對高層和低層異常都提供了最佳的功能:它允許拋出適當的高層異常,同時又能捕獲底層的原因進行分析。

六、每個方法拋出的異常都要有文檔
  

七、在細節消息中包含能捕獲失敗的異常
  

八、努力是失敗保持原子性
  對於在可變對象上執行操作的方法,獲得原子性最常見的辦法是,在執行操作之前檢查參數的有效性。

九、不要忽視異常
  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章