Java抽象類和接口的比較

         一個軟件設計的好壞,我想很大程度上取決於它的總體架構,而這個總體架構其實就是你對整個宏觀商業業務的抽象框架,當表明業務邏輯的高層抽象層結構 合理時,你底層的具體實現須要考慮的就僅僅是一些算法和一些具體的業務實現了。當你須要再開發另外一個相近的項目時,你之前的抽象層說不定還能夠再次利用 。面對對象的設計,複用的重點其實應該是抽象層的複用,而不是具體某一個代碼塊的複用html

        abstract class和interface是Java語言中對於抽象類定義進行支持的兩種機制,正是因爲這兩種機制的存在,才賦予了Java強大的面向對象能力。 abstract class和interface之間在對於抽象類定義的支持方面具備很大的類似性,甚至能夠相互替換,所以不少開發者在進行抽象類定義時對於 abstract class和interface的選擇顯得比較隨意。其實,二者之間仍是有很大的區別的,對於它們的選擇甚至反映出對於問題領域本質的理解、對於設計意圖的理解是否正確、合理。本文將對它們之間的區別進行一番剖析,試圖給開發者提供一個在兩者之間進行選擇的依據。算法

1、抽象類:

在瞭解抽象類以前,先來了解一下抽象方法。抽象方法是一種特殊的方法:它只有聲明,而沒有具體的實現。抽象方法的聲明格式爲:編程

abstract void fun();設計模式

  抽象方法必須用abstract關鍵字進行修飾。若是一個類含有抽象方法,則稱這個類爲抽象類,抽象類必須在類前用abstract關鍵字修飾。由於抽象類中含有無具體實現的方法,因此不能用抽象類建立對象api

        抽象類就是爲了繼承而存在的,若是你定義了一個抽象類,卻不去繼承它,那麼等於白白建立了這個抽象類,由於你不能用它來作任何事情。對於一個父類,若是它的某個方法在父類中實現出來沒有任何意義,必須根據子類的實際需求來進行不一樣的實現,那麼就能夠將這個方法聲明爲abstract方法,此時這個類也就成爲abstract類了架構

        在面向對象的概念中,咱們知道全部的對象都是經過類來描繪的,可是反過來卻不是這樣。並非全部的類都是用來描繪對象的,若是一個類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類就是抽象類。抽象類每每用來表徵咱們在對問題領域進行分析、設計中得出的抽象概念,是對一系列看上去不一樣,可是本質上相同的具體概念的抽象。好比:若是咱們進行一個圖形編輯軟件的開發,就會發現問題領域存在着圓、三角形這樣一些具體概念,它們是不一樣的,可是它們又都屬於形狀這樣一個概念,形狀這個概念在問題領域是不存在的,它就是一個抽象概念。正是由於抽象的概念在問題領域沒有對應的具體概念,因此用以表徵抽象概念的抽象類是不可以實例化的。框架

          包含抽象方法的類稱爲抽象類,但並不意味着抽象類中只能有抽象方法,它和普通類同樣,一樣能夠擁有成員變量和普通的成員方法。注意,抽象類和普通類的主要有三點區別:ide

  1)抽象方法必須爲public或者protected(由於若是爲private,則不能被子類繼承,子類便沒法實現該方法),缺省狀況下默認爲public。函數

  2)抽象類不能用來建立對象;工具

  3)若是一個類繼承於一個抽象類,則子類必須實現父類的抽象方法。若是子類沒有實現父類的抽象方法,則必須將子類也定義爲爲abstract類。

  在其餘方面,抽象類和普通的類並無區別。

        在抽象類中,若是方法沒有具體實現(就是方法後面沒有{}),那麼必須加上abstract來聲明這個方法,而接口中不須要使用abstract來聲明(抽象類之因此被稱爲抽象類,就是由於它包含有抽象方法。含有抽象方法的類叫作抽象類)

2、接口

接口,英文稱做interface,在軟件工程中,接口泛指供別人調用的方法或者函數。從這裏,咱們能夠體會到Java語言設計者的初衷,它是對行爲的抽象。在Java中,定一個接口的形式以下:

[public] interface InterfaceName {
}


  接口中能夠含有 變量和方法。可是要注意,接口中的變量會被隱式地指定爲public static final變量(而且只能是public static final變量,用private修飾會報編譯錯誤),而方法會被隱式地指定爲public abstract方法且只能是public abstract方法(用其餘關鍵字,好比private、protected、static、 final等修飾會報編譯錯誤),而且接口中全部的方法不能有具體的實現,也就是說,接口中的方法必須都是抽象方法。從這裏能夠隱約看出接口和抽象類的區別,接口是一種極度抽象的類型,它比抽象類更加「抽象」,而且通常狀況下不在接口中定義變量。

  要讓一個類遵循某組特意的接口須要使用implements關鍵字,具體格式以下:

class ClassName implements Interface1,Interface2,[....]{

}

  能夠看出,容許一個類遵循多個特定的接口。若是一個非抽象類遵循了某個接口,就必須實現該接口中的全部方法。對於遵循某個接口的抽象類,能夠不實現該接口中的抽象方法。

一、從語法定義層面

abstract class的方式定義Demo的方式以下

abstract class Demo {  
abstract void method1();  
abstract void method2();  
…  
}

interface的方式定義Demo的方式以下

interface Demo {  
void method1();  
void method2();  
…  
}

       1)抽象類能夠提供成員方法的實現細節,而接口中只能存在public abstract 方法;

  2)抽象類中的成員變量能夠是各類類型的,而接口中的成員變量只能是public static final類型的;

  3)接口中不能含有靜態代碼塊以及靜態方法,而抽象類能夠有靜態代碼塊和靜態方法;

  4)一個類只能繼承一個抽象類,而一個類卻能夠實現多個接口。

二、編程方式

從編程的角度來看,abstract class和interface均可以用來實現"design by contract"的思想。可是在具體的使用上面仍是有一些區別的。

首先,abstract class在Java語言中表示的是一種繼承關係,一個類只能使用一次繼承關係。可是,一個類卻能夠實現多個interface。也許,這是Java語言的設計者在考慮Java對於多重繼承的支持方面的一種折中考慮吧。

其次,在abstract class的定義中,咱們能夠賦予方法的默認行爲。可是在interface的定義中,方法卻不能擁有默認行爲,爲了繞過這個限制,必須使用委託,可是這會 增長一些複雜性,有時會形成很大的麻煩。

在接口不能定義默認行爲還存在另外一個比較嚴重的問題,那就是可能會形成維護上的麻煩。由於若是後來想修改類的界面(通常經過 abstract class或者interface來表示)以適應新的狀況(好比,添加新的方法或者給已用的方法中添加新的參數)時,就會很是的麻煩,可能要花費不少的時間(對於派生類不少的狀況,尤其如此)。可是若是界面是經過abstract class來實現的,那麼可能就只須要修改定義在abstract class中的默認行爲就能夠了。

三、設計理念

上面主要從語法定義和編程的角度論述了abstract class和interface的區別,這些層面的區別是比較低層次的、非本質的。本小節將從另外一個層面:abstract class和interface所反映出的設計理念,來分析一下兩者的區別。做者認爲,從這個層面進行分析才能理解兩者概念的本質所在。

1)抽象類是對一種事物的抽象,即對類抽象,而接口是對行爲的抽象。抽象類是對整個類總體進行抽象,包括屬性、行爲,可是接口倒是對類局部(行爲)進行抽象。舉個簡單的例子,飛機和鳥是不一樣類的事物,可是它們都有一個共性,就是都會飛。那麼在設計的時候,能夠將飛機設計爲一個類Airplane,將鳥設計爲一個類Bird,可是不能將 飛行 這個特性也設計爲類,所以它只是一個行爲特性,並非對一類事物的抽象描述。此時能夠將 飛行 設計爲一個接口Fly,包含方法fly( ),而後Airplane和Bird分別根據本身的須要實現Fly這個接口。而後至於有不一樣種類的飛機,好比戰鬥機、民用飛機等直接繼承Airplane便可,對於鳥也是相似的,不一樣種類的鳥直接繼承Bird類便可。從這裏能夠看出,繼承是一個 "是否是"的關係,而 接口 實現則是 "有沒有"的關係。若是一個類繼承了某個抽象類,則子類一定是抽象類的種類,而接口實現則是有沒有、具有不具有的關係,好比鳥是否能飛(或者是否具有飛行這個特色),能飛行則能夠實現這個接口,不能飛行就不實現這個接口。

2)設計層面不一樣,抽象類做爲不少子類的父類,它是一種模板式設計。而接口是一種行爲規範,它是一種輻射式設計。什麼是模板式設計?最簡單例子,你們都用過ppt裏面的模板,若是用模板A設計了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,若是它們的公共部分須要改動,則只須要改動模板A就能夠了,不須要從新對ppt B和ppt C進行改動。而輻射式設計,好比某個電梯都裝了某種報警器,一旦要更新報警器,就必須所有更新。也就是說對於抽象類,若是須要添加新的方法,能夠直接在抽象類中添加具體的實現,子類能夠不進行變動;而對於接口則不行,若是接口進行了變動,則全部實現這個接口的類都必須進行相應的改動。

前面已經提到過,abstarct class在Java語言中體現了一種繼承關係,要想使得繼承關係合理,父類和派生類之間必須存在"is a"關係,即父類和派生類在概念本質上應該是相同的(參考文獻〔3〕中有關於"is a"關係的大篇幅深刻的論述,有興趣的讀者能夠參考)。對於interface 來講則否則,並不要求interface的實現者和interface定義在概念本質上是一致的,僅僅是實現了interface定義的契約而已。爲了使論述便於理解,下面將經過一個簡單的實例進行說明。

考慮這樣一個例子,假設在咱們的問題領域中有一個關於Door的抽象概念,該Door具備執行兩個動做open和close,此時咱們能夠經過abstract class或者interface來定義一個表示該抽象概念的類型,定義方式分別以下所示

使用abstract class方式定義Door:

abstract class Door {  
abstract void open();  
abstract void close();  
}

使用interface方式定義Door:

interface Door {  
void open();  
void close();  
}

其餘具體的Door類型能夠extends使用abstract class方式定義的Door或者implements使用interface方式定義的Door。看起來好像使用abstract class和interface沒有大的區別。

若是如今要求Door還要具備報警的功能。咱們該如何設計針對該例子的類結構呢(在本例中,主要是爲了展現abstract class和interface反映在設計理念上的區別,其餘方面無關的問題都作了簡化或者忽略)?下面將羅列出可能的解決方案,並從設計理念層面對這些不一樣的方案進行分析。

解決方案一:

//簡單的在Door的定義中增長一個alarm方法,以下:
abstract class Door {  
abstract void open();  
abstract void close();  
abstract void alarm();  
} 
///或者
interface Door {  
void open();  
void close();  
void alarm();  
} 
//那麼具備報警功能的AlarmDoor的定義方式以下:
class AlarmDoor extends Door {  
void open() { … }  
void close() { … }  
void alarm() { … }  
} 
//或者
class AlarmDoor implements Door {  
void open() { … }  
void close() { … }  
void alarm() { … }  
}

這種方法違反了面向對象設計中的一個核心原則ISP(Interface Segregation Priciple),在Door的定義中把Door概念自己固有的行爲方法和另一個概念"報警器"的行爲方法混在了一塊兒。這樣引發的一個問題是那些僅僅依賴於Door這個概念的模塊會由於"報警器"這個概念的改變.

解決方案二:

既然open、close和alarm屬於兩個不一樣的概念,根據ISP原則應該把它們分別定義在表明這兩個概念的抽象類中。定義方式有:這兩個概念都使用abstract class方式定義;兩個概念都使用interface方式定義;一個概念使用abstract class方式定義,另外一個概念使用interface方式定義。

顯然,因爲Java語言不支持多重繼承,因此兩個概念都使用abstract class方式定義是不可行的。後面兩種方式都是可行的,可是對於它們的選擇卻反映出對於問題領域中的概念本質的理解、對於設計意圖的反映是否正確、合理。咱們一一來分析、說明。

若是兩個概念都使用interface方式來定義,那麼就反映出兩個問題:一、咱們可能沒有理解清楚問題領域,AlarmDoor在概念本質上究竟是Door仍是報警器?二、若是咱們對於問題領域的理解沒有問題,好比:咱們經過對於問題領域的分析發現AlarmDoor在概念本質上和Door 是一致的,那麼咱們在實現時就沒有可以正確的揭示咱們的設計意圖,由於在這兩個概念的定義上(均使用interface方式定義)反映不出上述含義。

若是咱們對於問題領域的理解是:AlarmDoor在概念本質上是Door,同時它有具備報警的功能。咱們該如何來設計、實現來明確的反映出咱們的意思呢?前面已經說過,abstract class在Java語言中表示一種繼承關係,而繼承關係在本質上是"is a"關係。因此對於Door這個概念,咱們應該使用abstarct class方式來定義。另外,AlarmDoor又具備報警功能,說明它又可以完成報警概念中定義的行爲,因此報警概念能夠經過interface方式定義。以下所示:

abstract class Door {  
abstract void open();  
abstract void close();  
}  
interface Alarm {  
void alarm();  
}  
class AlarmDoor extends Door implements Alarm {  
void open() { … }  
void close() { … }  
void alarm() { … }  
}

這種實現方式基本上可以明確的反映出咱們對於問題領域的理解,正確的揭示咱們的設計意圖。其實abstract class表示的是"is a"關係,interface表示的是"like a"關係,你們在選擇時能夠做爲一個依據,固然這是創建在對問題領域的理解上的,好比:若是咱們認爲AlarmDoor在概念本質上是報警器,同時又具備 Door的功能,那麼上述的定義方式就要反過來了。

結論

  1. Java接口和Java抽象類最大的一個區別,就在於Java抽象類能夠提供某些方法的部分實現,而Java接口不能夠(就是interface中只能定義方法,而不能有方法的實現,而在abstract class中則能夠既有方法的具體實現,又有沒有具體實現的抽象方法),這大概就是Java抽象類惟一的優勢吧,但這個優勢很是有用。若是向一個抽象類里加入一個新的具體方法時,那麼它全部的子類都一會兒都獲得了這個新方法,而Java接口作不到這一點,若是向一個Java接口裏加入一個 新方法,全部實現這個接口的類就沒法成功經過編譯了,由於你必須讓每個類都再實現這個方法才行,這顯然是Java接口的缺點。這個在個人另一篇博客mapreduce 新舊API 區別中有提到相似的問題,在新的mapreduce api中更傾向於使用抽象類,而不是接口,由於這更容易擴展。緣由就是上面劃線部分所說的。
  2. 一個抽象類的實現只能由這個抽象類的子類給出,也就是說,這個實現處在抽象類所定義出的繼承的等級結構中,而因爲Java語言的單繼承性,因此抽象類做爲類型定義工具的效能大打折扣。在這一點上,Java接口的優點就出來了,任何一個實現了一個Java接口所規定的方法的類均可以具備這個接口的類型,而一個類能夠實現任意多個Java接口,從而這個類就有了多種類型。(使用抽象類,那麼繼承這個抽象類的子類類型就比較單一,由於子類只能單繼承抽象類;而子類可以同時實現多個接口,由於類型就比較多。接口和抽象類均可以定義對象,可是隻能用他們的具體實現類來進行實例化。)
  3. 從第2點不難看出,Java接口是定義混合類型的理想工具,混合類代表一個類不只僅具備某個主類型的行爲,並且具備其餘的次要行爲。
  4. 結合一、2點中抽象類和Java接口的各自優點,具精典的設計模式就出來了:聲明類型的工做仍然由Java接口承擔,可是同時給出一個Java 抽象類,且實現了這個接口,而其餘同屬於這個抽象類型的具體類能夠選擇實現這個Java接口,也能夠選擇繼承這個抽象類,也就是說在層次結構中,Java 接口在最上面,而後緊跟着抽象類,這下兩個的最大優勢都能發揮到極至了。這個模式就是「缺省適配模式」。在Java語言API中用了這種模式,並且全都遵循必定的命名規範:Abstract +接口名。(A extends AbstractB implements interfaceC,那麼B便可以選擇實現(@Override)接口interfaceC中的方法,也能夠選擇不實現;A便可以選擇實現(@Override)抽象類AbstractB中的方法,也能夠選擇不實現)

abstract class和interface是Java語言中的兩種定義抽象類的方式,它們之間有很大的類似性。可是對於它們的選擇卻又每每反映出對於問題領域中的概念本質的理解、對於設計意圖的反映是否正確、合理,由於它們表現了概念間的不一樣的關係(雖然都可以實現需求的功能)。這其實也是語言的一種的慣用法,但願讀者朋友可以細細體會

參考文獻

一、淺析Java抽象類和接口的比較

二、Java 接口和抽象類區別(寫的很好,轉了)

三、深刻理解Java的接口和抽象類

相關文章
相關標籤/搜索