Effective Java 第三版——64. 通過物件的介面引用物件
Tips
書中的原始碼地址: https://github.com/jbloch/effective-java-3e-source-code
注意,書中的有些程式碼裡方法是基於Java 9 API中的,所以JDK 最好下載 JDK 9以上的版本。

Effective Java, Third Edition
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方法。 只有當程式依賴於額外的方法時,才應該使用這樣的類來引用它的例項,這種情況應該是非常少見的。
這三種情況並不是面面俱到的,而僅僅是為了傳達通過合適的類引用物件的情況。在實踐中,給定物件是否具有適當的介面應該是顯而易見的。如果是這樣,使用介面引用物件,程式將更加靈活和流行。如果沒有合適的介面,就使用類層次結構中提供所需功能的最小的具體類。