1. 程式人生 > >Effective Java 第三版——64. 通過對象的接口引用對象

Effective Java 第三版——64. 通過對象的接口引用對象

fec source edit 習慣 基類 客戶端代碼 沒有 stream lock

Tips
書中的源代碼地址:https://github.com/jbloch/effective-java-3e-source-code
註意,書中的有些代碼裏方法是基於Java 9 API中的,所以JDK 最好下載 JDK 9以上的版本。

技術分享圖片

64. 通過接口引用對象

條目 51中指出,應該使用接口而不是類作為參數類型。更通常地說,應該更喜歡使用接口而不是類來引用對象。如果存在適當的接口類型,那麽應該使用接口類型聲明參數、返回值、變量和屬性。真正需要引用對象的類的惟一時機是使用構造方法創建它的時候。為了具體說明這一點,考慮LinkedHashSet的情況,它是Set接口的一個實現。養成這樣的習慣:

// Good - uses interface as type
Set<Son> sonSet = new LinkedHashSet<>();

不要是下面這個樣子:

// Bad - uses class as type!
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();

如果養成了使用接口作為類型的習慣,那麽你的程序將更加靈活。如果決定要切換實現,只需在構造方法中更改類名(或使用不同的靜態工廠)。例如,第一個聲明可以改為:

Set<Son> sonSet = new HashSet<>();

所有的代碼都會繼續工作。周圍的代碼不知道舊的實現類型,所以它不會註意到這一變化。

有一點需要註意:如果原始實現提供了接口的常規約定不需要的某些特殊功能,並且代碼依賴於該功能,那麽新實現提供相同的功能至關重要。 例如,如果圍繞第一個聲明的代碼依賴於LinkedHashSet的排序策略,那麽在聲明中用HashSet替換LinkedHashSet是不正確的,因為HashSet不保證叠代順序。

那麽,為什麽要更改實現類型呢?因為第二個實現比原來的實現提供了更好的性能,或者因為它提供了原來的實現所缺乏的理想功能。例如,假設一個屬性包含一個HashMap實例。將其更改為EnumMap將提供更好的性能和與鍵的自然順序一致的叠代順序,但是只能在鍵類型為枚舉類型的情況下使用EnumMap。將HashMap更改為LinkedHashMap將提供可預測的叠代順序,性能與HashMap相當,而不需要對鍵類型作出任何特殊要求。

你可能認為使用變量的實現類型聲明變量是可以的,因為可以同時更改聲明類型和實現類型,但是不能保證這種更改能否正常編譯。如果客戶端代碼對原始實現類型使用了替換時不存在的方法,或者客戶端代碼將實例傳遞給需要原始實現類型的方法,那麽在進行此更改之後,代碼將不再編譯。使用接口類型聲明變量可以保持誠實可靠。

如果不存在適當的接口,則通過類而不是接口引用對象是完全合適的。 例如,考慮值類,例如String和BigInteger。 值類很少用多個實現類來編寫。 它們通常是final的,很少有相應的接口。 將這樣的值類用作參數,變量,屬性或返回類型是完全合適的。

沒有適當接口類型的第二種情況是屬於框架的對象,其基本類型是類而不是接口。 如果一個對象屬於這樣一個基於類的框架,最好用相關的基類來引用它,它通常是抽象類,而不是它的實現類。 許多java.io包下的類(如OutputStream)都屬於此類。

沒有適當接口類型的最後一種情況是實現接口的類,但也提供了在接口中找不到的額外方法——例如,PriorityQueue具有Queue接口上不存在的comparator方法。 只有當程序依賴於額外的方法時,才應該使用這樣的類來引用它的實例,這種情況應該是非常少見的。

這三種情況並不是面面俱到的,而僅僅是為了傳達通過合適的類引用對象的情況。在實踐中,給定對象是否具有適當的接口應該是顯而易見的。如果是這樣,使用接口引用對象,程序將更加靈活和流行。如果沒有合適的接口,就使用類層次結構中提供所需功能的最小的具體類。

Effective Java 第三版——64. 通過對象的接口引用對象