effective java 讀書筆記---第四章類與接口

20170411
13.要使類與成員的可訪問性最小
儘可能的使每個類或成員不被外界訪問
對於方法覆蓋,子類中的訪問級別不能低於父類的訪問級別
實例域決不能是公有的
靜態域除了靜態常量,也不應該是公有的
長度非零的數組總是可變的,所有靜態 final 數組域或者返回這種域的訪問方法幾乎總是錯誤的,因爲客戶端可以很容易的修改數組中的內容,解決這種問題有兩種方法,1.定義私有靜態數組域,並增加一個公有的不可變列表:

private static final Thing[] PRIVATE_THINGS = {...}
private static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_THINGS));

注意這種方法也僅僅只能防止數組元素的指向不能指向新的內存地址,不能保證數組指向的對象的域不會改變(unmodifiableList返回的 list 指向的對象與原list指向的對象地址一致,asList 也是如此)
第二種方法:定義私有靜態數組域,並增加一個公有方法,返回私有數組的一個備份
(這種方法返回數組也不能保證不會改變原有數組元素的對象內的域不被改變,僅能保證原有數組元素指向的地址不變),注意如果這兩種方法中所引用的元素對象是不可變的,則可以保證集合不可變

14.在公有類中使用公有方法而非公有域
如果類可以在它所在包的外部進行訪問,就提供訪問方法.但如果類是包級私有的,或者私有的嵌套類,直接暴露它的數據域並沒有本質的錯誤

15.使可變性最小化
如果要定義不可變類,需要遵循以下規則:
不要提供任何可以修改對象狀態的方法
保證類不會被擴展
所有的域都是 final
所有的域都是私有的
確保對於任何可變組件的互斥訪問,如果類持有指向可變對象的域,必須確保該類的客戶端無法獲得指向這些對象的引用,並且永遠不要用客戶端提供的對象引用來初始化這樣的域,也不要從任何訪問方法中返回該對象的引用.在構造器,訪問方法和 readObject方法中使用保護性拷貝
一般函數式的編程方法就使用以上邏輯,返回對象總是新建對象,而不是修改對象的引用
不可變對象本質上是線程安全的,他們不要求同步,不可變對象可以被自由的共享,因此不需要爲不可變對象提供 clone 方法或者拷貝構造器
不僅可以共享不可變對象,甚至可以共享他們的內部信息
不可變對象唯一缺點是,對於每一個不同的值都需要一個單獨的對象,而創建這些對象的代價可能很高
如果可以精確的預測出客戶端將要在不可變的類上執行哪些複雜的多階段操作,可以提供包級私有可變配套類的方法.如果無法預測,最好的方法是提供一個公有的可變配套類,例如 String 對象的可變配套類 StringBuilder(StringBuffer)
如果類不能作爲不可變類,也應該儘可能的降低它的可訪問性

16.複合優於繼承
對於包內使用或者專門爲繼承而設計並且具有很好的文檔說明的類來使用繼承是非常安全的.但對於普通具體類,進行跨包邊界的繼承則是非常危險的.
繼承打破了封裝性
如果不清楚超類方法的實現細節,繼承中覆蓋方法往往容易出錯,超類在後續的發行版本中可以獲得新的方法.而如果繼承如果只增加方法而不修改方法,也會出現問題,超類新的發行版本中可能含有與你定義的子類相同的方法
使用複合,可以有效避免上述問題,新類中每個實例方法都返回被包含的現有類實例對應的方法,這種被稱爲轉發
包裝類幾乎沒有缺點,但包裝類,不適合在回調框架中使用,會引發 SELF 問題
只有在子類是真正的超類子類型時才適用於繼承.包裝類不僅比子類更加健壯,而且功能也很強大

17.要麼爲繼承而設計,並提供文檔說明,要麼就禁止繼承
必須有文檔說明,它可覆蓋方法的自用性,類必須在文檔中說明,在那些情況下可能調用可覆蓋的方法
按照慣例,如果方法調用了可覆蓋的方法,在它的文檔註釋的末尾應該包含關於這些調用的描述信息:”This implementation…(該實現…)”,意味着這段描述關注該方法內部的工作情況.
好的 api 文檔應該描述該方法做了什麼工作,而不是它是如何做的
必須在發佈類之前先編寫子類進行測試(一般而言3個子類即可完成測試工作)
構造器決不能調用可被覆蓋的方法.子類中覆蓋版本的方法將會在子類構造器運行之前被調用,如果該覆蓋方法依賴於子類構造器所執行的任何初始化工作,程序將會失敗
clone 與 readObject 都不可以調用可被覆蓋的方法
readObject,覆蓋版本的方法將會在子類狀態被反序列化之前運行
clone,覆蓋版本方法則是在子類的 clone 方法有機會修正被克隆對象的狀態之前運行
如果類實現了Serializable,並且該類有一個readResolve 或者 writeReplace 方法,就必須使得這兩個方法稱爲 prodected 的否則子類會忽略掉這兩個方法
如果標準的類沒有實現標準接口,爲安全,繼承應該禁止調用任何可被覆蓋的方法,完全消除這個類中可覆蓋方法的自用性,就可以安全的進行子類化,可以將可覆蓋方法的代碼移植到私有的輔助方法中

18.接口優於抽象類
java 只允許單繼承
可以提供骨架實現類
jdk1.8之後接口可以提供默認實現(子類可以不再實現這種提供了默認實現的方法),接口幾乎完全具備抽象類的所有功能(接口僅能持有 public static final 的變量,方法全部是 public)

19.接口只用於定義類型
常量接口是對接口的不良使用
如果常量與現有類或者接口緊密相連,應該將這些常量置於相應的類或者接口中,如果這些常量應該被看成枚舉類型,就應該使用枚舉類型,否則應該使用不可實例化的工具類.如果大量使用工具類常量,可以使用靜態導入,避免需要使用類名來修飾常量

20.類層次優於標籤類
標籤類過於臃腫,容易出錯,並且效率低下.使用子類型化代替標籤類,標籤類是類層次的一種簡單的仿效
類層次可以用來反映類型之間本質上的層次關係
標籤類很少有適用的時候

21.用函數對象表示策略
策略模式,聲明一個接口來表示該策略,並且爲每一個具體的策略聲明一個實現了該接口的類,當一個具體策略只被使用一次時使用匿名內部類,當被設計用來重複使用時,通常被實現爲私有的靜態成員類,並通過公有的靜態 final 域被導出

22.優先考慮靜態成員類
嵌套類被設計成只爲它的外圍類提供服務,嵌套類如果可能使用到其他環境,就應該設計成頂層類.嵌套類有四種:靜態成員類,非靜態成員類,匿名類和局部類,除了第一種,其他三種都被稱作內部類
靜態成員類,可以被看做是普通類.
非靜態成員類,需要依賴於外圍類才能初始化,每一個實例都持有外圍類的引用
如果聲明成員類,不要求訪問外圍實例,就要始終把static 修飾符放到它的聲明中,如果忽略 static,那麼每一個實例都將包含一個外圍類引用,保留這份引用要消耗時間與空間,並且導致外圍實例符合垃圾回收時依然得以保留.如果沒有外圍實例,就不能使用非靜態成員類
匿名類主要用來動態創建函數對象
局部類(在任何可以創建局部對象的地方都可以創建局部類(一般爲方法內部),局部類也可以分爲靜態與非靜態)

發佈了52 篇原創文章 · 獲贊 32 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章