《Effective Java》讀書筆記

《Effective Java》讀書筆記

1.靜態工廠方法

  • 相對公有構造器的優勢
    • 有名字。靜態工廠方法可以根據功能定義名字,但構造器名字都是類名。
    • 不必每次調用時都創建對象。如果經常請求創建相同的對象,並且創建對象的代價很高,則這項技術可以極大的提高性能。
    • 可以返回原返回類型的任何子類型。這樣在選擇返回類時,更加靈活。
    • 在創建參數化實例的時候,它們使得代碼更加簡潔。
  • 缺點
    • 類如果不含有公有的或者受保護的構造器,就不能被子類化。因為在創建子類對象時,需要調用到父類的公有無參數構造器方法。
    • 它們與其他的靜態方法實際上沒有任何區別。因為在API文檔中未被明確標記,因此對於提供了靜態工廠方而不是構造器的類來說,要想查明如何實例化一個類,這是非常困難的。
  • 總結靜態工廠方法和公有構造器各有用途,但是應該首選考慮用靜態工廠方法替代構造器。

2.Singleton實現機制

  • 三種方式:
    • 功能上和公有域近似。
    • 優勢:
    • 代碼簡潔
    • 無償提供序列化機制
    • 防止多次序列化
    • 構造器方法設置為private,導出一個final的public靜態成員。
    • 構造器方法設置為private,利用公有靜態工廠方法導出final的private靜態成員。
    • 包含單個元素的枚舉類型
  • 總結:
    • 單元素的枚舉類型是實現Singleton的最佳方式,是jdk1.5開始有的。
    • 前兩種方式核心思想:構造器設置為private+導出公有靜態成員


3.內存洩漏的3個來源

  • 類自己管理內存
    • 解決辦法:一旦元素被釋放,就應該將該元素中包涵的任何對象引用清空。
    • 例子:Stack類,存放元素的數組為elementData。在pop操作後,就應該執行elementData[elementCount] = null;從而使得GC可以對它裡面保存引用的對象進行清理(elementData中保存的是對象引用,而不是對象本身)
  • 緩存
    • 緩存應定時清掉無效的項。這項清理工作可以由一個後臺線程來完成
    • 在給緩存添加條目時順便進行清理
    • 解決辦法
  • 監聽器和其他回調
    • 解決辦法:回調立即被當作垃圾回收的最佳方式是:只保存它們的弱引用。弱引用:被弱引用關聯的對象只能生存到下一次垃圾回收之前


4. equals()方法分析

  • 覆蓋equals時的通用規定
    • 自反性:對象必須等於自身
    • 對稱性:任何兩個對象對於“它們是否相等”的問題都必須保持一致。
    • 傳遞性:a和b相等,b和c相等,則a和c相等。
    • 一致性:如果兩個對象相等,則它們始終保持相等,除非它們中有一個被修改了。
    • 非空性:所有的對象都必須*不等於*null常見錯誤用法:許多類的equals方法第一句都是:if(o==null) return false;但這樣寫是冗餘的。因為equals方法在把參數轉為適當類型前,必須調用instanceOf操作符,檢查其參數是否為正確的類型。null在進行instanceOf檢查類型時,如果傳入參數為null,則直接返回false。
  • 高質量equals()書寫原則:
    • 使用==操作符檢查“參數是否為這個對象的引用”。如果是,則返回true。這是一種性能優化,如果比較操作可能很昂貴,則值得這麼做。
    • 使用instanceOf操作符檢查“參數是否為正確的類型”。
    • 把參數轉換成正確的類型。
    • 對該類中每個關鍵域,檢查參數中的域是否與該對象中對應的域匹配。查看jdk源碼可以發現,基本都是這樣寫的,格式完全一致
  • 常用用法說明:
    • 非float、double的基本類型,直接用==進行比較即可。
    • 對象引用域,則遞歸調用equals方法。
    • 對於float域,則用Float.cmopare()方法。
    • 對於double域,則用Double.compare()方法。
  • 覆寫equals時,必須覆蓋hashCode()方法
    • 這一點很重要,因為任何兩個equal的對象,在調用hashCode()方法時,必須返回同一個整數。


5.接口vs抽象類

  • 最大區別為實現抽象類定義的類型,類必須為抽象類的子類。
  • 接口的優勢
    • 現有的類可以很容易被更新,以實現新的接口對於一個新的接口,如果一個類想實現它,則只要在類的聲明中添加一個implement子句,同時override接口中的方法即可。
    • 接口時定義mixin(混合類型)的理想選擇mixing類型是指:類除了實現它的“基本類型”以外,還可以實現這個mixin類型,以表明它提供了某些可供選擇的行為。比如JKD中的Clone接口、Comparable接口。一個類如果實現了Comparable接口,則表明這個類的實例可以和其它的可相互比較的對象進行排序。
    • 接口允許構造非層次結構的類型框架


6.列表vs數組

  • 不同點
    • 數組是協變的,泛型是不可變的。協變:如果Sub為Super的子類型,則數組類型Sub[]為Super[]的子類型不可變:任意兩個不同的類型Type1和Type2,List既不是List的子類型,也不是List的超類型
    • 數組是具體化的。數組元素的類型:在程序運行時才檢查類型約束泛型:在編譯時強化類型信息,運行時擦除類型信息
  • 為什麼創建泛型數組是非法的?因為它不是類型安全的。因為數組元素的類型檢查發生在運行時,如果定義了一個泛型數組,則其類型不能在編譯時進行檢查,如果定義出現錯誤,則會在程序運行時發生ClassCastException異常。這就違背了泛型系統提供的保證。因為泛型是在編譯時進行類型檢查,故不會出現運行時的ClassCastException。


7.可變參數

  • 機制
    • 先創建一個數組
    • 然後將參數值傳入到數組中
    • 最後將數組傳遞給方法
  • 性能問題可變參數方法的每次調用都會導致進行一次數組分配和初始化。


8.基本類型vs裝箱類型

  • 主要區別
    • 基本類型只有值,而裝箱類型則具有與它們的值不同的同一性。解釋:換句話說,兩個裝箱基本類型可以有相同的功能值,但是有不同的同一性。
    • 基本類型只有功能完備的值,但裝箱類型除了有對應的功能值之外,還有一個非功能值null。
    • 基本類型比裝箱類型節省空間和時間。
  • 裝箱類型的合理用處
    • 作為集合中的元素、鍵、值– 在參數化類型中,必須使用裝箱類型
    • 在進行反射方法的調用時,必須使用裝箱類型


9.native方法

  • 定義是指本地程序設計語言(比如c或者c++)來編寫的特殊方法
  • 用途
    • 提供了“訪問特定於平臺的機制”的能力
    • 提供了訪問遺留代碼庫的能力
  • 缺點
    • 程序不再能免受內存損壞錯誤的影響。因為本地語言是不安全的
    • 程序不再可自由移植。因為本地語言是與平臺相關的
    • 程序難調試
    • 進入和退出native方法需要相關開銷
    • 需要“膠合代碼”的本地方法編寫起來單調乏味,難以閱讀


10.序列化

  • 序列化的代價
    • 最大代價是:一旦一個類被髮布,就大大降低改變這個類的實現的“靈活性”如果一個類實現了Serializable接口,它的字節流編碼就變成了它的導出API的一部分。一旦這個類被廣泛使用,往往必須永遠支持這種序列化形式。
    • 它增加了出現Bug和安全漏洞的可能性。因為序列化機制是一種語言之外的對象創建機制,反序列化機制都是一個“隱藏的構造器”,因為沒有顯示的構造器,所以很容易忘記確保:反序列化過程必須也要保證“由真正的構造器建立起來的約束關係”,並且不允許攻擊者訪問正在構造過程中的對象的內部信息。
    • 隨著類發行新的版本,相關的測試負擔增加。
    • 當一個可序列化的類被修訂時,很重要的一點就是:檢查是否可以“在新版本中序列化一個實例,然後在舊版本中反序列化”,反之亦然。
  • 使用默認序列化的缺點
    • 它使得這個類的導出API永遠束縛在該類的呃內部表示法上
    • 消耗過多的空間
    • 消耗過多的時間因為序列化邏輯不瞭解對象圖的拓撲關係,所以它必須要經過一個昂貴的圖遍歷過程。
    • 引起棧溢出默認的序列化過程要對對象圖執行一次遞歸遍歷,即使對於中等規模的對象圖,也可能引起棧溢出。


分享到:


相關文章: