Java介面和抽象類的區別

2020-07-16 10:05:16
從前面對物件導向的設計原則的講解,讀者可以了解到,其實所有的設計原則和設計模式都離不開抽象,因為只有抽象才能實現上述設計原則和設計模式。

在 Java 中,針對抽象有兩種實現方式:一種是介面,一種是抽象類。很多讀者對這兩種實現方式比較困惑,到底是使用介面,還是使用抽象類呢?對於它們的選擇甚至反映出對問題領域本質的理解,對設計意圖的理解是否正確、合理?

在物件導向的設計思想中,所有的物件都是通過類來描繪的,但是反過來,並不是所有的類都是用來描繪物件的,如果一個類中沒有描繪一個具體的物件,那麼這樣的類就是抽象類,抽象類是對那些看上去不同,但是本質上相同的具體概念的抽象,正是因為抽象的概念在問題領域沒有對應的具體概念,所以抽象類是不能夠範例化的。

基本語法區別

在 Java 中,介面和抽象類的定義語法是不一樣的。這裡以動物類為例來說明,其中定義介面的示意程式碼如下:
public interface Animal {
    // 所有動物都會吃
    public void eat();

    // 所有動物都會飛
    public void fly();
}
定義抽象類的示意程式碼如下:
public abstract class Animal {
    // 所有動物都會吃
    public abstract void eat();

    // 所有動物都會飛
    public void fly(){};
}
可以看到,在介面內只能是功能的定義,而抽象類中則可以包括功能的定義和功能的實現。在介面中,所有的屬性肯定是 public、static 和 final,所有的方法都是 abstract,所以可以預設不寫上述識別符號;在抽象類中,既可以包含抽象的定義,也可以包含具體的實現方法。

在具體的實現類上,介面和抽象類的實 現類定義方式也是不一樣的,其中介面實現類的示意程式碼如下:
public class concreteAnimal implements Animal {
    // 所有動物都會吃
    public void eat(){}

    // 所有動物都會飛
    public void fly(){}
}
抽象類的實現類示意程式碼如下:
public class concreteAnimal extends Animal {
    // 所有動物都會吃
    public void eat(){}

    // 所有動物都會飛
    public void fly(){}
}
可以看到,在介面的實現類中使用 implements 關鍵字;而在抽象類的實現類中,則使用 extends 關鍵字。一個介面的實現類可以實現多個介面,而一個抽象類的實現類則只能實現一個抽象類。

設計思想區別

從前面抽象類的具體實現類的實現方式可以看出,其實在 Java 中,抽象類和具體實現類之間是一種繼承關係,也就是說如果釆用抽象類的方式,則父類別和子類在概念上應該是相同的。介面卻不一樣,如果採用介面的方式,則父類別和子類在概念上不要求相同。介面只是抽取相互之間沒有關係的類的共同特徵,而不用關注類之間的關係,它可以使沒有層次關係的類具有相同的行為。因此,可以這樣說:抽象類是對一組具有相同屬性和方法的邏輯上有關係的事物的一種抽象,而介面則是對一組具有相同屬性和方法的邏輯上不相關的事物的一種抽象。

仍然以前面動物類的設計為例來說明介面和抽象類關於設計思想的區別,該動物類預設所有的動物都具有吃的功能,其中定義介面的示意程式碼如下:
public interface Animal {
    // 所有動物都會吃
    public void eat();
}

定義抽象類的示意程式碼如下:
public abstract class Animal {
    // 所有動物都會吃
    public abstract void eat();
}
不管是實現介面,還是繼承抽象類的具體動物,都具有吃的功能,具體的動物類的示意程式碼如下。

介面實現類的示意程式碼如下:
public class concreteAnimal implements Animal {
    // 所有動物都會吃
    public void eat(){}
}
抽象類的實現類示意程式碼如下:
public class concreteAnimal extends Animal {
    // 所有動物都會吃
    public void eat(){}
}
當然,具體的動物類不光具有吃的功能,比如有些動物還會飛,而有些動物卻會游泳,那麼該如何設計這個抽象的動物類呢?可以別在介面和抽象類中增加飛的功能,其中定義介面的示意程式碼如下:
public interface Animal {
    // 所有動物都會吃
    public void eat();

    // 所有動物都會飛
    public void fly();
}
定義抽象類的示意程式碼如下:
public abstract class Animal {
    // 所有動物都會吃
    public abstract void eat();

    // 所有動物都會飛
    public void fly(){};
}
這樣一來,不管是介面還是抽象類的實現類,都具有飛的功能,這顯然不能滿足要求,因為只有一部分動物會飛,而會飛的卻不一定是動物,比如飛機也會飛。那該如何設計呢?有很多種方案,比如再設計一個動物的介面類,該介面具有飛的功能,示意程式碼如下:
public interface AnimaiFly {
    // 所有動物都會飛
    public void fly();
}

那些具體的動物類,如果有飛的功能的話,除了實現吃的介面外,再實現飛的介面,示意程式碼如下:
public class concreteAnimal implements Animal,AnimaiFly {
    // 所有動物都會吃
    public void eat(){}

    // 動物會飛
    public void fly();
}

那些不需要飛的功能的具體動物類只實現具體吃的功能的介面即可。另外一種解決方案是再設計一個動物的抽象類,該抽象類具有飛的功能,示意程式碼如下:
public abstract class AnimaiFly {
    // 動物會飛
    public void fly();
}
但此時沒有辦法實現那些既有吃的功能,又有飛的功能的具體動物類。因為在 Java 中具體的實現類只能實現一個抽象類。一個折中的解決辦法是,讓這個具有飛的功能的抽象類,繼承具有吃的功能的抽象類,示意程式碼如下:
public abstract class AnimaiFly extends Animal {
    // 動物會飛
    public void fly();
}
此時,對那些只需要吃的功能的具體動物類來說,繼承 Animal 抽象類即可。對那些既有吃的功能又有飛的功能的具體動物類來說,則需要繼承 AnimalFly 抽象類。

但此時對用戶端有一個問題,那就是不能針對所有的動物類都使用 Animal 抽象類來進行程式設計,因為 Animal 抽象類不具有飛的功能,這不符合物件導向的設計原則,因此這種解決方案其實是行不通的。

還有另外一種解決方案,即具有吃的功能的抽象動物類用抽象類來實現,而具有飛的功能的類用介面實現;或者具有吃的功能的抽象動物類用介面來實現,而具有飛的功能的類用抽象類實現。

具有吃的功能的抽象動物類用抽象類來實現,示意程式碼如下:
public abstract class Animal {
    // 所有動物都會吃
    public abstract void eat();
}
具有飛的功能的類用介面實現,示意程式碼如下:
public interface AnimaiFly {
    // 動物會飛
    public void fly();
}
既具有吃的功能又具有飛的功能的具體的動物類,則繼承 Animal 動物抽象類,實現 AnimalFly 介面,示意程式碼如下:
public class concreteAnimal extends Animal implements AnimaiFly {
    // 所有動物都會吃
    public void eat(){}

    // 動物會飛
    public void fly();
}
或者具有吃的功能的抽象動物類用介面來實現,示意程式碼如下:
public interface Animal {
    // 所有動物都會吃
    public abstract void eat();
}
具有飛的功能的類用抽象類實現,示意程式碼如下:
public abstract class AnimaiFly {
    // 動物會飛
    public void fly(){};
}
既具有吃的功能又具有飛的功能的具體的動物類,則實現 Animal 動物類介面,繼承 AnimaiFly 抽象類,示意程式碼如下:
public class concreteAnimal extends AnimaiFly implements Animal {
    // 所有動物都會吃
    public void eat(){}

    // 動物會飛
    public void fly();
}
這些解決方案有什麼不同呢?再回過頭來看介面和抽象類的區別:抽象類是對一組具有相同屬性和方法的邏輯上有關係的事物的一種抽象,而介面則是對一組具有相同屬性和方法的邏輯上不相關的事物的一種抽象,因此抽象類表示的是“is a”關係,介面表示的是“like a”關係。

假設現在要研究的系統只是動物系統,如果設計人員認為對既具有吃的功能又具有飛的功能的具體的動物類來說,它和只具有吃的功能的動物一樣,都是動物,是一組邏輯上有關係的事物,因此這裡應該使用抽象類來抽象具有吃的功能的動物類,即繼承 Animal 動物抽象類,實現 AnimalFly 介面。

如果設計人員認為對既具有吃的功能,又具有飛的功能的具體的動物類來說,它和只具有飛的功能的動物一樣,都是動物,是一組邏輯上有關係的事物,因此這裡應該使用抽象類來抽象具有飛的功能的動物類,即實現 Animal 動物類介面,繼承 AnimaiFly 抽象類。

假設現在要研究的系統不只是動物系統,如果設計人員認為不管是吃的功能,還是飛的功能和動物類沒有什麼關係,因為飛機也會飛,人也會吃,則這裡應該實現兩個介面來分別抽象吃的功能和飛的功能,即除實現吃的 Animal 介面外,再實現飛的 AnimalFly 介面。

從上面的分析可以看出,對於介面和抽象類的選擇,反映出設計人員看待問題的不同角度,即抽象類用於一組相關的事物,表示的是“isa”的關係,而介面用於一組不相關的事物,表示的是“like a”的關係。