第四章、類和接口(二)

第十七條、要么為繼承而設計,并提供文檔說明,要么就禁止繼承

  1. 該類的文檔必須精確地描述覆蓋每個方法所帶來的影響,即說明它可覆蓋的方法的自用性。

  2. 為了繼承而設計的類,對這個類會有一些實質性的限制。如:構造器決不能調用可被覆蓋的方法。另一種合理的辦法是確保這個類永遠不會調用它的任何可覆蓋的方法。


第十八條、接口優于抽象類

  1. 接口和抽象類是Java用來定義允許多個實現的類型的兩種機制。

    它們之間最明顯的區別在于:抽象類允許包含某些方法的實現,但是接口則不允許。一個更為重要的區別在于:為了實現抽象類定義的類型,類必須成為抽象類的一個子類。而任何一個類,只要它定義了所有必要的方法,并且遵守通用約定,它就被允許實現一個接口,而不管這個類是處于類層次的哪個位置。因為Java只允許單繼承,不可能有一個以上的父類,所以抽象類作為類型定義受到了極大的限制。

  2. 接口的優點:

    • 現有的類很容易被更新,以實現新的接口:如果這些方法尚不存在,你需要做的只是增加必要的方法,然后在類聲明中增加一個implements子句。而一般來說,無法更新現有的類來擴展新的抽象類。

    • 接口是定義mixin(混合類型)的理想選擇。mixin類型是指:類除了實現它的“基本類型”之外,還可以實現這個mixin類型,以表明它提供了某種可供選擇的行為。如Comparable接口,這樣的接口之所以被稱為mixin,是因為它允許任選的功能可被混合到類型的主要功能中。

    • 接口允許我們構造非層次結構的類型框架。有些事物是不能被整齊地組織成一個嚴格的層次結構。

  3. 通過對你導出的每個重要接口都提供一個抽象的骨架實現類(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;
             }
         };
     }
    
  4. 骨架實現的美妙之處在于:它們為抽象類提供了實現上的幫助,但又不強加“抽象類被用作類型定義時”所特有的嚴格限制。骨架實現類能夠有助于接口的實現,實現了這個接口的類可以把對于接口方法的調用,轉發到一個內部私有類的實例上,這個內部私有類擴展了骨架實現類(這就是模擬多重繼承)。

  5. 編寫骨架實現類需要認真地研究接口,首先確定哪些方法是最為基本的,其他的方法可以根據它們來實現。這些基本方法將成為骨架實現類中的抽象方法。然后,必須為接口中的所有其他方法提供具體的實現。下面是一個實例:

     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();
         }
     }
    
  6. 抽象類與使用接口相比有一個明顯的優勢:

    抽象類的演變比接口的演變要容易得多。如果在后續的發行版本中,你希望在抽象類中增加新方法,始終可以增加具體的包含合理的默認實現的方法。而接口是行不通的。因此,設計公有的接口要非常謹慎,接口一旦被公開發行,并且已經被廣泛實現,再想改這個接口幾乎是不可能的。

  7. 總結:

    接口通常是定義允許多個實現類型的最佳途徑,這條規則有個例外,即當演變的容易性比靈活性和功能更為重要的時候。在這種情況下,應該使用抽象類來定義類型,前提是必須理解和可以接受這些局限性。
    如果你導出一個重要的接口,就應該堅決考慮同時提供骨架實現類。最后應該盡可能謹慎地設計所有的公有接口,并通過編寫多個實現來對它們進行全面的測試。


第十九條、接口只用于定義類型

當類實現接口時,接口就充當可以引用這個類的實例的類型(type),因此,類實現了接口就表明客戶端可以對這個類的實例實施某種動作。
為了任何其他目的而定義接口是不恰當的,比如常量接口,這種接口沒有包含任何方法,只包含靜態的final域,每個域都導出一個常量。


第二十條、類層次優于標簽類

  1. tagged class 標簽類:帶有兩種甚至更多種風格的實例的類,并包含表示實例風格的標簽(tag)域。這種標簽類有著許多缺點,過于冗長、容易出錯且效率低。

  2. 子類型化(sub typing),變成類層次。它們可以用來反映類型之間本質上的層次關系,有助于增強靈活性,并進行更好的編譯時類型檢查。


第二十一條、用函數對象表示策略

  1. 有些語言支持函數指針(function pointer)、代理(delegate)、lambda表達式,或者支持類似的機制,允許程序把“調用特殊函數的能力”存儲起來并傳遞這種能力。這種機制通常用于允許函數的調用者通過傳入第二個函數來指定自己的行為。策略模式

  2. Java沒有提供函數指針,但是可以用對象引用(實質上也是地址)實現同樣的功能

     class StringLengthComparator{
         public int compare(String s1,String s2){
             return s1.length() - s2.length();
         }
     }
    

    調用對象上的方法通常是執行該對象上的某項操作。然而,我們也可能定義這樣一種對象,它的方法執行其他對象上這些對象被顯式傳遞給這些方法上的操作。如果一個類僅僅導出這樣的一個方法,它的實例實際上就等同于一個指向該方法的指針。這樣的實例被稱為函數對象(function object)。

    作為典型的具體策略類,StringLengthComparator類是無狀態的(stateless):沒有域。所以這個類的所有實例在功能上都是等價,作為Singleton是非常合適的。指向StringLengthComparator對象的引用可以被當做是一個指向該比較器的函數指針,可以在任意一對字符串上被調用。即StringLengthComparator實例是用于字符串比較操作的具體策略。

  3. 我們在設計具體的策略類時,還需要定義一個策略接口,具體的策略類往往使用匿名類聲明。需要注意的是,以匿名類的方法,每次執行都會創建一個新的實例。

     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();
         }
     });
    
  4. 總結:

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

第二十二條、優先考慮靜態成員類

  1. 嵌套類(nested class)是指被定義在一個類內部的類。存在的目的應該只是為了外圍類提供服務。
    嵌套類有四種:靜態成員類、非靜態成員類、匿名類局部類。除了第一種類,其他三種都被稱為內部類(inner class)

  2. 靜態成員類是最簡單的一種嵌套類,它可以訪問外圍類的所有成員,它遵守同樣的可訪問性規則。

    一種常見的用法是作為公有的輔助類。如果聲明成員類不要求訪問外圍實例,就要始終把static修飾符放在它的聲明中。如果不?每個實例都將包含一個額外的指向外圍對象的引用,耗時又占空間。

  3. 非靜態成員類的每個實例都隱含著與外圍類的一個實例相關:

    如果嵌套類的實例可以在它的外圍類實例之外獨立存在,則必須是靜態成員類。
    當非靜態成員類的實例被創建的時候,它和外圍實例之間的關聯也隨之被建立起來,而且這種關聯關系以后不能被修改。
    非靜態成員類的一種常見用法是定義一個Adapter,它允許外部類的實例被看作是另一個不相關的類的實例。

  4. 匿名類不與其他的成員一起被聲明和實例化,只有在使用的同時被聲明和實例化??梢猿霈F在任何允許存在表達式的地方。

    匿名類的常見用法:動態地創建函數對象、創建過程對象(如Runable、Thread等實例)和在靜態工廠方法的內部。

  5. 局部類用的最少。在任何可以聲明局部變量的地方都可以聲明局部類。

  6. 總結:

    四種嵌套類有各自的用途:
    如果一個嵌套類需要在單個方法之外依然是可見的、或者太長,就應該使用成員類。
    如果成員類的每個實例都需要一個指向其外圍實例的引用,就要把成員類做成非靜態,否則就是靜態的。假設這個嵌套類屬于一個方法的內部.
    如果你只需要在一個地方創建實例,并且已經有了一個預置的類型可以說明這個類的特征,就把它做成匿名類,否則就是局部類。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,791評論 6 545
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,795評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,943評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,057評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,773評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,106評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,082評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,282評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,793評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,507評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,741評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,220評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,929評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,325評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,661評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,482評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,702評論 2 380

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,836評論 18 139
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,738評論 18 399
  • (一)Java部分 1、列舉出JAVA中6個比較常用的包【天威誠信面試題】 【參考答案】 java.lang;ja...
    獨云閱讀 7,136評論 0 62
  • 1、一個".java"源文件中是否可以包括多個類(不是內部類)?有什么限制?答:可以有多個類,但只能有一個publ...
    岳小川閱讀 957評論 0 2
  • 渲染的方案之前探索過很多,但是很遺憾,那些方案都是基于系統控件,并沒有接觸到真正的OpenGL。網上也沒有太多Ma...
    偶是星爺閱讀 887評論 0 2