Effective Java筆記——第4章類和接口

第13條:使類和成員的可訪問性最小化

好處:降低系統模塊之間(包之間、類之間)的耦合,使得模塊可以獨立測試開發,提高可重用性和易維護性

終極目標:儘可能的使每個類或者類的成員不被外接訪問。

保護級別和公有的都是類導出的API的一部分,必須永遠得到支持,應儘量少用。

注意:
  1、包級私有——聲明該成員的包內部的任何類都可以訪問這個成員,同時,這也是缺省訪問級別,如果沒有爲成 員指定訪問修飾符,就採用這個訪問級別。
  2、子類中的訪問級別不容許低於超類中的訪問級別,以確保任何可以使用超類的地方都可以使用子類。
  3、如果一個類實現了某個接口,那麼接口中所有的方法在這個類中必須申明爲公有的,因爲接口中所有的方法都 隱含着公有的訪問級別。
  4、除了包含靜態的final的,指向基本類型或者不可變對象的引用的公有域之外,不能有任何公有域。(域是非 final的,或是指向可變對象的final引用,那麼一旦是這個域成爲公用的,就放棄了對存儲在這個域中的值進行限制的能力,就放棄了這個域不可變的能力,包含公有域的類並不是線程安全的。假設常量構成了類提供的整個抽象中的一部分,可以通過公有的靜態final域來暴露這些常量。按慣例,這些域的名稱由大寫字母組成,單詞之間用下劃線隔開。很重要的一點是,這些域要麼包含基本類型和值要麼包含指向不可變對象的引用。長度非零的數組總是可變的,類具有公有的靜態final數組域,或者返回這種域的訪問方法,這幾乎總是錯誤的。)

第14條:在公有類中使用訪問方法而非公有域

  公有類永遠不應該暴露可變的域,讓公有類暴露不可變的域其危害比較小。但是,用包級私有或者私有的嵌套類來暴露域是可以的。

第15條:使可變性最小化

不可變類:只是其實例不能被修改的類。每個實例中包含的所有信息都必須在創建該實例的時候就提供,並在對象的整個生命週期內固定不變。java平臺中不可變的類有:String、基本類型的包裝類、BigInteger和BigDecimal類。

使類成爲不可變,要遵循5條規則:
  1、不要提供任何會修改對象狀態的方法
  2、保證類不會被擴展(防止子類化的方法有兩種:一是使這個類成爲final,二是將類的所有構造函數設爲私有的或者包級私有的,並添加公有的靜態工廠來代替公有的構造器)
  3、使所有的域都是final的
  4、使所有的域都成爲私有的
  5、確保對於任何可變組件的互斥訪問

注意:爲了提高性能,以上規則可變寬鬆。比如,許多不可變的類擁有一個或者多個非final的域,它們在第一次被請求執行這些計算的時候,把一些開銷昂貴的計算結果緩存在這些域中。如果將來再次請求同樣的計算,就直接返回這些緩存的值,從而節約了重新計算的開銷,因爲對象是不可變的,它的不可變形保證了這些計算如果再次執行,就會產生同樣的結果。

不可變對象:
  1、不可變對象本質上是線程安全的,它們不要求同步。
  2、不僅可以共享不可變對象,甚至也可以共享它們的內部信息。
  3、不可變對象爲其他對象提供了大量的構件。
  4、不可變對象的缺點是:對於每個不同的值都需要一個單獨的對象。

第16條:符合優先於繼承

這裏說的繼承是指實現繼承(一個類擴展另一個類),而不是接口繼承

對於包內部的繼承或者繼承專門爲了繼承而設計並且有很好的文檔的類,是安全的。

不推薦的是:跨越包的界限繼承普通類,除非你確認子類確實是超類的子類型

跨包繼承可能存在的風險可能有:

  1、繼承打破了封裝性,子類的實現依賴於超類中特定功能的實現細節,但是超類可能在後續版本中獲得更新,子類所要求的特定細節就無法保證

  2、超類可能添加新的方法,而使得子類的某些實現依賴的特定條件被打破

  3、超類中可能添加一個與子類中籤名相同但返回值不同的方法,從而使得子類不可通過編譯

複合:不擴展現有類,而是在新的類中增加一個引用現有類實例的引用。新類的每個實例都可以調用被包含的現有類實例中對應的方法,並返回它的結果,這樣的方式叫做“轉發”,新的類也叫做“包裝類”,除了編碼稍有點瑣碎,包裝類在性能與內存佔用上都不會帶來很大的影響,但是安全性有了很大提高。

注意:包裝類不適合用在回調框架中,在回調框架中,對象把自身的引用傳遞給其他的對象,用於後續的調用。因爲被包裝的對象並不知道它外面的包裝對象,所以它傳遞一個指向自身的引用(this),回調時避開了外面的包裝對象。

第17條:要麼爲繼承而設計,並提供文檔說明,要麼就禁止繼承

好的API文檔應該描述一個方法做了什麼,而不是描述它是如何做到的。

編寫爲繼承而設計的類,有如下建議:

  1、編寫子類進行測試,一般3個子類就足以測試

  2、構造器決不能調用可被覆蓋的方法,無論是間接還是直接調用:因爲超類的構造器先於子類的構造器運行,如 果在超類的構造器中調用了可覆蓋的方法,那麼子類中的覆蓋方法就會在子類構造器被調用之前被超類構造器提前調用,如若該覆蓋方法的執行依賴於子類構造器的某些初始化條件,則難以預期執行。

  3、最好不要實現Cloneable和Serializable接口,因爲會把一些實質性的負擔轉嫁給擴展這個類的程序員,若需如此:由於clone與readObject方法與構造器方法類似,所以他們都不可直接或間接調用可覆蓋的方法;如果有readResolve或者writeReplace方法,那應該使他們成爲受保護的方法,以避免被擴展該類的程序員忽略。

第18條:接口優於抽象類

接口和抽象類的區別:
     1、抽象類允許包含某些方法的實現,但是接口不允許。
     2、爲了實現由抽象類定義的類型,類必須成爲抽象類的一個子類。

接口優於抽象類的地方:
     1、現有的類可以很容易被更新,以實現新的接口
    2、接口是定義混合類型的理想選擇:比如可比較行爲
    3、利用接口的繼承可以構造非層次結構的類型框架:比如同時繼承歌唱家和作曲家

抽象類較於接口的優勢:
    1、 演變更加方便,即如需添加新的方法,那麼只需在抽象類中增加方法並給出默認的實現,那麼所有繼承他的子類也就可以提供這個方法,而對於接口則不行。
    2、接口一旦被公開發行,並且已被廣泛實現,再想改變這個接口幾乎是不可能的。

    接口通常是定義容許多個實現的最佳途徑,除非演變的容易性比靈活性和功能更加重要的時候;若設計了重要的接口,則應堅決考慮提供骨架類。通過對導出的每個重要接口都提供一個抽象的骨架實現類,把接口和抽象類的優點結合起來。
    骨架類的實現:首先,必須認真研究接口,並確定哪些方法是基本的,其他的方法則可以根據他們來實現。這些基本方法將成爲骨架實現類中的抽象方法。然後,必須爲接口中所有其他的方法提供具體的實現。骨架類實現了重要的接口,同時又是爲繼承的目的而設計的。

第19條:接口只用於定義類型

常量接口:不包含任何方法,它只包含靜態的final域,每個域都導出一個常量。這種做法是不可取的,接口應該只被用來定義類型,它們不應該被用來導出常量。

導出常量的幾種正確做法:

  1、如若常量與類或接口密切相關,就把常量寫入該類或接口

  2、使用枚舉類型

  3、使用不可實例化的工具類(Utility)

第20條:類層次優於標籤類

標籤類:即一些使用比如type域來區別某個實力的類型。比如使用使用一個標籤來區分形狀類的實例是圓形還是矩形

壞處:

  1、代碼可讀性變差:會有很多樣板代碼,即在類中對不同的type會有不同的代碼、type的判斷以及枚舉等。

  2、增加內存佔用:比如矩形實例中也會保存圓形type的東西。

  3、不能定義final域:比如在矩形中,長寬域理應在final的,即應該在構造函數中初始化。但是在圓形中這兩個域卻又是無用的,但因爲它是final又必須初始化,這樣就需要藉助對type的判斷來分別初始化。

  4、構造器不能借助編譯器來設置標籤域並初始化正確的域:無論你的type是矩形還是圓形,對於編譯器來說都是屬於同一類的實例,編譯器纔不知道你的type是啥呢

  5、無法增加type:除非能修改源碼,且增加type必須在判斷type的所有地方都做相應修改

使用類層次來代替標籤類: 

  1、把依賴type值的方法寫成abstract方法放到抽象類中。

  2、所有type類型都有的數據域放到上述抽象類中。

  3、不依賴於type標籤值的方法,則在上述抽象類中實現。

總之:標籤類很少有適用的場合

第21條:用函數對象表示策略

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

第22條:優先考慮靜態成員類

嵌套類:指定義在另一個類的內部的類,嵌套類存在的目的應該只是爲了他的外圍類提供服務。嵌套類有四種:靜態成員類、非靜態成員類、匿名類和局部類,非靜態成員類、匿名類和局部類稱爲內部類。

靜態成員類:可以訪問外圍類的所有成員,包括私有的成員。私有靜態成員類只有在外圍類的內部才能被訪問。靜態成員類的一種常見用法是作爲公有的輔助類,私有靜態成員類的一種常見用法是用來代表外圍類所代表的對象的組件。如果嵌套類的實例可以在它外圍類的實例之外獨立存在,這個嵌套類就必須是靜態成員類。

非靜態成員類:非靜態成員類的每個實例都隱含着與外圍類的一個外圍實例上的方法,或利用修飾過的this構造獲得外圍實例的引用。當非靜態成員類的實例被創建的時候,他和外圍實例之間的關聯關係也隨之被建立起來,而且這種關係以後不能被修改。非靜態成員類的一種常見用法是定義一個Adapter。

匿名類:當嵌套類屬於一個方法的內部,且只需在一個地方創建實例且有一個預置類型可以說明這個類的特徵

成員類:如果一個嵌套類需要在方法之外仍然是可見的,或者太長以至於不適合放在方法內部,就應該使用成員類

注意:如果聲明成員類不要求訪問外圍實例,就要始終把static修飾符放在它的聲明中,使它成爲靜態成員類。如果省略了static修飾符,則每個實例都包含一個額外的指向外圍對象的引用。保存這份引用要消耗時間和空間,並且會導致外圍實例在符合垃圾回收時卻仍然得以保留。





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