第十七條、要么為繼承而設計,并提供文檔說明,要么就禁止繼承
該類的文檔必須精確地描述覆蓋每個方法所帶來的影響,即說明它可覆蓋的方法的自用性。
為了繼承而設計的類,對這個類會有一些實質性的限制。如:構造器決不能調用可被覆蓋的方法。另一種合理的辦法是確保這個類永遠不會調用它的任何可覆蓋的方法。
第十八條、接口優于抽象類
-
接口和抽象類是Java用來定義允許多個實現的類型的兩種機制。
它們之間最明顯的區別在于:抽象類允許包含某些方法的實現,但是接口則不允許。一個更為重要的區別在于:為了實現抽象類定義的類型,類必須成為抽象類的一個子類。而任何一個類,只要它定義了所有必要的方法,并且遵守通用約定,它就被允許實現一個接口,而不管這個類是處于類層次的哪個位置。因為Java只允許單繼承,不可能有一個以上的父類,所以抽象類作為類型定義受到了極大的限制。
-
接口的優點:
現有的類很容易被更新,以實現新的接口:如果這些方法尚不存在,你需要做的只是增加必要的方法,然后在類聲明中增加一個implements子句。而一般來說,無法更新現有的類來擴展新的抽象類。
接口是定義mixin(混合類型)的理想選擇。mixin類型是指:類除了實現它的“基本類型”之外,還可以實現這個mixin類型,以表明它提供了某種可供選擇的行為。如Comparable接口,這樣的接口之所以被稱為mixin,是因為它允許任選的功能可被混合到類型的主要功能中。
接口允許我們構造非層次結構的類型框架。有些事物是不能被整齊地組織成一個嚴格的層次結構。
-
通過對你導出的每個重要接口都提供一個抽象的骨架實現類(skeletal implementation),把接口和抽象類的優點結合起來。
接口的作用依然是定義類型,但是骨架實現類接管了所有與接口實現相關的工作。按照慣例,骨架實現類成為AbstractInterface,這里的Interface指實現的接口名。如AbstractCollection、AbstractSet等。如果設計得當,骨架實現可以使程序員很容易提供他們自己的接口實現。
//下面是一個靜態工廠方法,它包含一個完整的、功能全面的List實現。 static List<Integer> intArrayAsList(final int[] a){ if(a == null) throw new NullPointerException(); return new AbstractList<Integer>() { @Override public Integer get(int i) { return a[i]; //AutoBoxing } @Override public Integer set(int i,Integer val){ int oldVal = a[i]; a[i] = val; return oldVal; } @Override public int size() { return a.length; } }; }
骨架實現的美妙之處在于:它們為抽象類提供了實現上的幫助,但又不強加“抽象類被用作類型定義時”所特有的嚴格限制。骨架實現類能夠有助于接口的實現,實現了這個接口的類可以把對于接口方法的調用,轉發到一個內部私有類的實例上,這個內部私有類擴展了骨架實現類(這就是模擬多重繼承)。
-
編寫骨架實現類需要認真地研究接口,首先確定哪些方法是最為基本的,其他的方法可以根據它們來實現。這些基本方法將成為骨架實現類中的抽象方法。然后,必須為接口中的所有其他方法提供具體的實現。下面是一個實例:
import java.util.AbstractList; import java.util.List; import java.util.Map; /** * Created by laneruan on 2017/6/8. */ public abstract class AbstractMapEntry<K,V> implements Map.Entry<K,V> { public abstract K getKey(); public abstract V getValue(); @Override public V setValue(V value) { throw new UnsupportedOperationException(); } @Override public boolean equals(Object o){ if(o == this) return true; if(!(o instanceof Map.Entry)) return false; Map.Entry<?,?> arg = (Map.Entry) o ; return equals(getKey(),arg.getKey()) && equals(getValue(),arg.getValue()); } public static boolean equals(Object o1,Object o2){ return o1 == null ? o2 == null : o1.equals(o2); } @Override public int hashCode(){ return hashCode(getKey()) ^ hashCode(getValue()); } public static int hashCode(Object obj){ return obj == null ? 0 : obj.hashCode(); } }
-
抽象類與使用接口相比有一個明顯的優勢:
抽象類的演變比接口的演變要容易得多。如果在后續的發行版本中,你希望在抽象類中增加新方法,始終可以增加具體的包含合理的默認實現的方法。而接口是行不通的。因此,設計公有的接口要非常謹慎,接口一旦被公開發行,并且已經被廣泛實現,再想改這個接口幾乎是不可能的。
-
總結:
接口通常是定義允許多個實現類型的最佳途徑,這條規則有個例外,即當演變的容易性比靈活性和功能更為重要的時候。在這種情況下,應該使用抽象類來定義類型,前提是必須理解和可以接受這些局限性。
如果你導出一個重要的接口,就應該堅決考慮同時提供骨架實現類。最后應該盡可能謹慎地設計所有的公有接口,并通過編寫多個實現來對它們進行全面的測試。
第十九條、接口只用于定義類型
當類實現接口時,接口就充當可以引用這個類的實例的類型(type),因此,類實現了接口就表明客戶端可以對這個類的實例實施某種動作。
為了任何其他目的而定義接口是不恰當的,比如常量接口,這種接口沒有包含任何方法,只包含靜態的final域,每個域都導出一個常量。
第二十條、類層次優于標簽類
tagged class 標簽類:帶有兩種甚至更多種風格的實例的類,并包含表示實例風格的標簽(tag)域。這種標簽類有著許多缺點,過于冗長、容易出錯且效率低。
子類型化(sub typing),變成類層次。它們可以用來反映類型之間本質上的層次關系,有助于增強靈活性,并進行更好的編譯時類型檢查。
第二十一條、用函數對象表示策略
有些語言支持函數指針(function pointer)、代理(delegate)、lambda表達式,或者支持類似的機制,允許程序把“調用特殊函數的能力”存儲起來并傳遞這種能力。這種機制通常用于允許函數的調用者通過傳入第二個函數來指定自己的行為。策略模式
-
Java沒有提供函數指針,但是可以用對象引用(實質上也是地址)實現同樣的功能。
class StringLengthComparator{ public int compare(String s1,String s2){ return s1.length() - s2.length(); } }
調用對象上的方法通常是執行該對象上的某項操作。然而,我們也可能定義這樣一種對象,它的方法執行其他對象上這些對象被顯式傳遞給這些方法上的操作。如果一個類僅僅導出這樣的一個方法,它的實例實際上就等同于一個指向該方法的指針。這樣的實例被稱為函數對象(function object)。
作為典型的具體策略類,StringLengthComparator類是無狀態的(stateless):沒有域。所以這個類的所有實例在功能上都是等價,作為Singleton是非常合適的。指向StringLengthComparator對象的引用可以被當做是一個指向該比較器的函數指針,可以在任意一對字符串上被調用。即StringLengthComparator實例是用于字符串比較操作的具體策略。
-
我們在設計具體的策略類時,還需要定義一個策略接口,具體的策略類往往使用匿名類聲明。需要注意的是,以匿名類的方法,每次執行都會創建一個新的實例。
class StringLengthComparator implements Comparator<String>{ public int compare(String s1,String s2){ return s1.length() - s2.length(); } } public interface Comparator<T>{ public int compare(T t1,T t2); } Arrays.sort(stringArray,new Comparator<String>(){ public int compare(String s1,String s2){ return s1.length() - s2.length(); } });
總結:
在Java中實現這種策略模式,需要聲明一個接口來表示這個策略,并且為每個具體策略聲明一個實現了該接口的類。當一個具體的策略只被使用一次時,通常使用匿名類來聲明和實例化這個具體策略類。當一個具體策略是設計用來重復使用的時候,它的類通常就要被實現為私有的靜態成員類,并通過公有的靜態final域被導出,其類型為該策略接口。
第二十二條、優先考慮靜態成員類
嵌套類(nested class)是指被定義在一個類內部的類。存在的目的應該只是為了外圍類提供服務。
嵌套類有四種:靜態成員類、非靜態成員類、匿名類和局部類。除了第一種類,其他三種都被稱為內部類(inner class)-
靜態成員類是最簡單的一種嵌套類,它可以訪問外圍類的所有成員,它遵守同樣的可訪問性規則。
一種常見的用法是作為公有的輔助類。如果聲明成員類不要求訪問外圍實例,就要始終把static修飾符放在它的聲明中。如果不?每個實例都將包含一個額外的指向外圍對象的引用,耗時又占空間。
-
非靜態成員類的每個實例都隱含著與外圍類的一個實例相關:
如果嵌套類的實例可以在它的外圍類實例之外獨立存在,則必須是靜態成員類。
當非靜態成員類的實例被創建的時候,它和外圍實例之間的關聯也隨之被建立起來,而且這種關聯關系以后不能被修改。
非靜態成員類的一種常見用法是定義一個Adapter,它允許外部類的實例被看作是另一個不相關的類的實例。 -
匿名類不與其他的成員一起被聲明和實例化,只有在使用的同時被聲明和實例化??梢猿霈F在任何允許存在表達式的地方。
匿名類的常見用法:動態地創建函數對象、創建過程對象(如Runable、Thread等實例)和在靜態工廠方法的內部。
局部類用的最少。在任何可以聲明局部變量的地方都可以聲明局部類。
-
總結:
四種嵌套類有各自的用途:
如果一個嵌套類需要在單個方法之外依然是可見的、或者太長,就應該使用成員類。
如果成員類的每個實例都需要一個指向其外圍實例的引用,就要把成員類做成非靜態,否則就是靜態的。假設這個嵌套類屬于一個方法的內部.
如果你只需要在一個地方創建實例,并且已經有了一個預置的類型可以說明這個類的特征,就把它做成匿名類,否則就是局部類。