碼出高效讀書筆記:重新思考接口和抽象類以及內部類

1、接口和抽象類

正如面向對象四大特徵:抽象、繼承、封裝、多態所述,定義類的過程就是抽象和封裝的過程。而接口和抽象類則是對實體類進行更高層次的抽象,僅定義公共行爲和特徵。兩者的共同點是都不能被實例化,但可以通過定義引用變量指向實例對象。

下表示接口和抽象類的語法區別:

 

語法維度 抽象類 接口
定義關鍵字 abstract interface
子類繼承或實現關鍵字 extends implements
方法實現 可以有 不能有,JDK8以後,允許有default實現
方法訪問控制符 無限制 有限制,默認是public  abstract類型
屬性訪問控制符 無限制 有限制,默認是public static final類型
靜態方法 可以有 不能有,JDK8以後,允許有
static{}靜態代碼塊 可以有 不能有
本類型之間擴展 單繼承 多繼承
本類型之間擴展關鍵字 extends extends

抽象類在被繼承時體現的是is-a關係,而接口再被實現時體現的是can-do關係。

與接口相比,抽象類通常是對同類事物相對具體的抽象,通常包含抽象方法、實體方法、屬性變量。如果一個抽象類只有一個抽象方法,那麼它等同於一個接口。

is-a關係需要符合裏式代換原則(即父類適用的地方,用子類來替代同樣適用。舉個例子:警匪片中,警察經常說,放下武器!而對面的匪徒有的使用匕首,有的使用手槍,這些都是武器的子類。父類出現的地方,即“放下武器”,那麼,“放下匕首”、“放下手槍”都是對的)。

can-do關係要符合接口隔離原則。實現類要有能力去實現並執行接口中定義的行爲,例如Plan can fly.Brid can fly.中應該把fly這個動作/功能定義成一個接口,而不是把fly()放在某個抽象類中,再由Plane和Brid利用is-a關係去繼承此抽象類。因爲嚴格意義上來說,除了fly這個行爲外,在Plane和Brid兩者中很難再找到相同或者相似的特徵。


抽象類是模板設計,而接口是契約式設計。

抽象類包含一組相對具體的特徵,性格偏內向,比如某品牌特定型號的汽車,底盤結構、控制電路、剎車系統等是抽象出來的共同特徵,但根據動感型、舒適性、豪華型的區分,內飾、車頭燈、顯示屏等可以存在不同版本的實現。

接口是開放的,性格偏外箱,它就像是一份合同,定義了方法名、參數、返回值,甚至是拋出的異常類型。誰都可以來實現它,但如果向實現它的類就必須遵守這份接口約定合同,比如,任何類型的車輛都必須實現如下的接口:

public interface VehicleSafe{
    /**
     * @param initSpeed 剎車時的初始速度
     * @param brakeTime 從initSpeed開始剎車到停止行駛的時間,單位是毫秒
     * @return 從initSpeed開始剎車到停止行駛的距離
     */
    double brake(int initSpeed,int brakeTime);

剎車是一個開放式的強制行爲規範,任何車輛都必須具有剎車的能力,要明確在特定初速度的情況下,剎車時間多長,剎車距離多長。此規範對任何車輛都是強約束的,這就是契約。


接口是頂級的“類”,對然關鍵字是interface,但是編譯後的字節碼擴展名還是.class。抽象類是二當家,接口位於頂層,而抽象類對各個接口進行了組合,然後實現部分接口行爲,其中AbstaractCollection是最典型的抽象類:

public abstract class AbstractCollection<E> implements Collection<E>{
    //Collection定義的抽象方法,但本類沒有實現
    //Collection接口定義的方法,size()這個方法對於鏈表和順序表有不同的實現方式
    public abstract int size();

    //實現Collection接口的這個方法,因爲對AbstractCollection的子類它們的判空方式是一致的,這就是
    //模板式設計,對於所有它的子類,實現共同的方法體,通過多態調用到子類的具體size()實現
    
    public boolean isEmpty(){
        //實現Collection的方法
        return size() == 0;
    }

    //其他屬性和部分方法實現.....
}

Java語言中類的繼承採用單繼承形式,避免繼承氾濫、菱形繼承、循環繼承,甚至“四不像”實現類的出現。在JVM中,一個類如果有多個直接父類,那麼綁定機制會變得非常複雜。

接口繼承接口,關鍵字是extends,而不是implements,允許多重繼承是因爲接口有契約式的行爲約定,沒有任何具體實現和屬性,某個實體類在實現多重繼承後的藉口時,只是說明“can do many things”。

當糾結定義接口還是抽象類時,優先推薦定義爲接口,遵循接口隔離原則,按某個維度劃分爲多個接口,然後再用抽象類去implements某些接口,這樣做可方便後續的擴展和重構。

2、內部類

在一個.java的源文件中,只能定義一個類名與文件名完全一致的公開類,並使用public class關鍵字來修飾。但是在面嚮對象語言中,任何一個類都可以在內部定義另外一個類,前者爲外部類,後者爲內部類。內部類本身就是類的一個屬性,與其他屬性定義方式一致。

比如:屬性字段private static String str,由訪問控制符、是否靜態類型、屬性類型、變量名組成。而內部類private static class Inner{},也是按照這樣的順序來定義的。類型可以爲class、enum,甚至是interface,當然在內部類中定義接口是不推薦的。

內部類可以是靜態和非靜態的,它可以出現在屬性定義、方法體和表達式中,甚至可以匿名出現(匿名內部類),具體分爲如下四種:

  1. 靜態內部類,如:static class StaticInnerClass{};
  2. 成員內部類,如:private class InstanceInnerClass{};
  3. 局部內部類,定義在方法或者表達式內部;
  4. 匿名內部類,如:(new Thread(){}).start()。

如下是最精簡的4中內部類定義方式:

public class OuterClass{
    
    //成員內部類
    private class InstanceInnerClass{}

    //靜態內部類
    static class StaicInnerClass{}

    public static void main(String[] args){
        //兩個匿名內部類
        (new Thread() {}).start();
        (new Thread() {}).start();

        //兩個方法內部類
        class MethodClass1{}
        class MethodClass2{}
    }
}

無論是什麼類型的內部類,都會編譯成一個獨立的.class文件,上面這個類編譯以後會產生如下的.class文件:

  • OuterClass.class
  • OuterClass$1.class
  • OuterClass$1MethodClass1.class
  • OuterClass$1MethodClass2.class
  • OuterClass$2.class
  • OuterClass$InstanceInnerClass.class
  • OuterClass$StaticInnerClass.class

外部類與內部類之間使用$符號分隔,匿名內部類使用數字進行編號,而方法內部類,在類名前還有一個編號來標識是哪個方法。

匿名內部類和靜態內部類是比較常用的方式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章