1. 程式人生 > >Java中接口和抽象類的比較

Java中接口和抽象類的比較

系列 分享 space 日誌信息 pub 指向 相關 最好的 就會

Java中接口和抽象類的比較-2013年5月寫的讀書筆記摘要

1. 概述

接口(Interface)和抽象類(abstract class)是 Java 語言中支持抽象類的兩種機制,是Java程序設計使用多態性的基礎[[1]]

(在面向對象語言中,接口的多種不同的實現方式即為多態。

多態性是同意你將父對象設置成為和一個或很多其它的他的子對象的技術。賦值之後。父對象就能夠依據當前賦值給它的子對象的特性以不同的方式運作(摘自“Delphi4編程技術內幕”)。

簡單的說。就是一句話:同意將子類類型的指針賦值給父類類型的指針[[2]])。

在那些面向對象(object)的程序設計語言的概念中,類(

classss指的是一種抽象的數據類型、是客觀對象在人腦中的主觀反映、是對象共性的抽象、類型同樣事物數據的抽象。

能夠說,所有的對象都須要通過類來進行描寫敘述,可是所有的類卻不一定都是用來對對象來進行描寫敘述的。

假設某一個類中所包括的信息不足以用來描寫敘述一個詳細的對象,那麽我們就稱其為抽象類(abstract class)抽象類是我們在對某一問題領域進行設計和分析時所得出的抽象概念。是一系列本質上同樣,而外在形象各異的詳細概念的抽象反映[[3]]。

比方:假設我們進行一個圖形編輯軟件的開發。就會發現問題領域存在著圓、三角形這樣一些詳細概念,它們是不同的,可是它們又都屬於形狀這樣一個概念,形狀這個概念在問題領域是不存在的,它就是一個抽象概念。正是由於抽象的概念在問題領域沒有相應的詳細概念,所以用以表示抽象概念的抽象類是不可以實例化對象的。

接口好比是程序之間的一個約定或合同,但它僅僅定義了行為的協議,並未定義履行接口協議的詳細方法。接口中僅僅指定抽象方法的方法頭。但不提供抽象方法的詳細實現。接口定義的僅僅是實現某種特定功能的一組對外接口和規範,而其詳細功能的實現是在實現這個接口的各個類中完畢的[1],一個類實現了某個接口,我們就說這個類符合了某個約定。

接口表示一種能力[4],一個類實現了某個接口。就表示這個類具備了某種能力。

生活中一個人能夠具有多項能力,一個類也能夠實現多個接口。

抽象類強調的是“概念”,接口強調的是“能力”(或者說是“行為”)。

2.Java中的接口和抽象類的相似之處

(1)抽象類和接口都能實現對一組行為的抽象

。接口和抽象類都可包括抽象方法。繼承(實現)它們的類實例必須所有實現它們定義的抽象方法後才幹用於實例化對象(只是,抽象類能夠一個抽象方法都沒有);

(2)接口和抽象類木身都不能用於對象實例化但它們的引用能夠指向繼承(實現)它們的類實例從而動態地使用這些類實例。

3. Java中的接口和抽象類的差別

1) 屬性抽象類中能夠有變量,而接口中不能定義變量。即接口中屬性都會自己主動用public static final來修飾,都是全局靜態常量。且必須在定義時指定初始值。

2) 方法抽象類中能夠有詳細方法和抽象方法,抽象類的子類也能夠是抽象類;而接口中全部方法都是抽象方法。接口中方法都會自己主動用public abstract修飾。

因為接口不涉及實現。從這點上看,接口在抽象化程度方面比抽象類的更高

3) 繼承性:抽象類屬於一種特殊的類。類的繼承必須滿足一個子類僅僅能有一個父類(單繼承),但能夠實現若幹個接口(多實現)。而接口能夠實現多繼承,即一個接口能夠有多個父接口。

4) 設計思想[1]: 這一點也是最本質的一點,對於抽象類與接口的選擇非常重要。對於抽象類來說,抽象類與其子類之間存在一個is a"的繼承關系,即二者形成層次結構父類和子類在概念本質上是同樣的

技術分享

如Shape類與其子類Circle , Square ,Triangle在本質上是同樣的,明顯存在一個“is a"關系。即Circle(圓形)、Square(矩形),Triangle(三角形)都屬於Shape形狀。

對於接口來說.接口與實現它的類之間不存在仟何層次關系

也不要求接口和實現它的類之間在概念本質上一致,僅僅要求接口的實現者實現接口規定的功能。接口能夠實現毫不相關類的同樣行為。比抽象類的使用更加方便靈活。比方定義一個接口open_close表示開關行為:

interface open_close{

void open();

void close();

}

門door有開關功能。能夠讓Door類實現open_close接口。

class Door implements open_close {

void open() {……}

void close() {……}

}

手機MobileTelephone有開關功能。能夠讓mobileTelephone類實現open_close接口。

class MobileTelephone implements open_close {

void open() {……}

void close() {……}

}

電腦computer有開關功能。能夠讓Computer類實現open_close接口。

class Computer implements open_close {

void open() {……}

void close() {……}

}

從該例能夠看出,類Door, MobileTelephone,Computer與接口open_close之間不存在不論什麽層次關系。它們之間是“has”關系,即Door, MobileTelephone,Computer都具有接口open_close指定的功能。通過接口open_close實現了毫不相關類Door, MobileTelephone,Computer的共同行為。

再舉個不太精確,可是好理解的樣例。在學校的學生信息管理軟件中“我是一個能打籃球、踢足球的學生”能夠定義為:

class Me extends Student implementsIPlayBasketeball, IPlayFootball { }

我是學生,因此我extends Student,我有打籃球,踢足球的能力。因此我實現IPlayBasketeball, IPlayFootball,我就擁有了IPlayBasketeball, IPlayFootball接口提供的各種行為(如。傳球。頭球,射門等)。而且我自己實現它們。


4.接口與抽象類的選擇[4]

考慮這樣一個樣例,如果我們在為一個電器生產廠家開發手機軟件。問題領域中有一個關於手機的抽象概念,該手機具一些動作如開機,關機等。此時我們能夠通過接口或者抽象類來定義一個表示該抽象概念的類型,定義方式分別例如以下所看到的:

使用接口方式定義手機:

interface Mobile_phone{

void open();

void close();

}

使用抽象類方式定義手機:

abstract class Mobile_phone{

abstract void open();

abstract void close();

}

而詳細的手機類型(如A18型,B30型)能夠繼承抽象類方式定義。或者使用實現接口的方式定義。看起來好像使用接口和抽象類沒有大的差別。但隨著技術的發展,假設如今要求手機要集成有信用卡的功能。我們應該怎樣設計針對該樣例的類結構呢?信用卡的一些基木功能有,電子錢包,消費等,這些功能和手機的開機關機功能屬於兩個不同的概念,依據接口隔離原則(ISP,Interface Segregation Principle)應該把它們分別定義在代表這兩個概念的抽象表示(接口或者抽象類)中。這時“信用卡”這個抽象概念的定義可能是這兩種情況:

interface Creditcard {

void e_wallet();

void consume();

}

abstract class Creditcard {

abstract void a_ wallet();

abstract void consume();

}

這時手機和銀行卡這兩個概念的定義方式就有了四種可能的組合,例如以下表:

手機

信用卡

方案A

定義為抽象類

定義為抽象類

方案B

定義為抽象類

定義為接口

方案C

定義為接口

定義為抽象類

方案D

定義為接口

定義為接口

方案A(兩個概念都定義為抽象類)能夠立即排除,由於Java不支持多繼承。實現類這無法同一時候繼承這兩個抽象類。在這裏能夠看到抽象類在通過概念的組合來擴展功能的時候是

不方便的

其它的方式眼下從語法上都是可行的,但誰更合理是值得考究的。

先研究一下方案C(“手機”定義為接口;“信用卡”定義為抽象類),這時實現這兩個概念的類和這兩個概念之間的關系就是,“like”手機,“is”信用卡,也就是說實現手機接口、繼承信用卡抽象類的子類具有手機的功能,可是本質上是信用卡。顯然這和我們對問題領域的理解不符。由於帶信用卡功能的“手機”在概念本質上是手機。同一時候具有信用卡的功能能夠像信用卡一樣使用)。

所以這個方法是不合理的。

除非我們是在為信用卡的制造商寫軟件。他們希望增加手機的功能。並且,假設手機再擴展功能。如電子地圖。導航器等等,把這樣的擴展的功能概念像信用卡”一樣定義為抽象類的話,由於Java的單繼承機制。這是無法實現的。

這道理和方案A一樣,把用來擴展功能的概念定義為抽象類並不合適。

方案B(“手機”定義為抽象類。“信用卡”定義為接口)應該是眼下最合理的設計了,這時反映出的概念是is手機,like“信用卡”。

假設有擴展功能的話。能夠再定義成接口,成為“is手機。like信用卡like電子地圖”。從而正確的反應我們面對的問題域。

那方案D兩者都定義為接口,是不是就不行呢?相對方案C來說。方案D的設計沒有反映出手機”是問題領域的本質的主體。使人有究竟我們在搞手機還是信用卡還是別的什麽東西?”這個疑問。這個缺點是不容置疑的。但從還有一方面來說:“手機”這個概念定義成接口。在軟件規模擴大的前提下,或許為以後其它的組件的使用提供了方便。比方說,如果廠家又有一個遙控器”的概念要我們設計。要把手機的功能設計進去,這時時候手機”如果是接口就方便了,implements他即可。

所以說。方案D是犧牲了概念的清晰性。得到了擴展

假設預見到問題領域以後沒有太大變化,方案B是最好的。

方案D在眼下是不合適的,但在以後的擴展中或許非常方便。

這裏得到的結論就是:假設僅僅是在定義一組行為框架的話。抽象類合適用來定義問題領域中的本質的抽象概念,接口合適用來定義擴展功能的抽象概念。

在剛剛這個樣例中“手機”。“信用卡”不過一組抽象方法。也就是概念中含有的不過行為框架沒有實現,這時候定義成抽象類或接口都有自己的道理。假設概念中己經含有了實現,這時候就把該概念定義成抽象類了。

比方一個“A系列打印機”的抽象類,由他定義不同類型的打印機,那一系列的打印機打印頁頭。頁腳的方案都是一樣的。但打印頁面主體比較復雜,各種詳細型號的打印機的各有它們不同的打印方法。這時能夠這麽設計:

方案一:依照打印機應該打印完整頁面的自然邏輯, PrintBody()抽象方法是打印機這個概念的一部分。設計為抽象類:

abstract class A_SeriesPrinter{

abstract protected void PrintBody();

public void OutReport() {

PrintHeader();

PrintBody();

PrintFooter();

}

protected void Draw(String str) { /*實現的代碼*/ }

protected void PrintHeader() { Draw("Head");/*實現的代碼*/ }

protected void PrintFooter() { Draw("Footer");/*實現的代碼*/ }

}

}

繼承抽象類的代碼:

classXXPrinter extends A_ SeriesPrinter {

protected void PrintBody() { /*實現的代碼‘/ }

}

classYYPrinter extends A_SeriesPrinter {

protected void PrintBody() { /*實現的代碼‘/ }

}

運用的代碼:

XXPrinter xx = new XXPrinter();

xx.OutReport();

YYPrinter yy = new YYPrinter();

yy.OutReport();

顯然這個方法是簡單而清楚的。

方案二:為了擴展性,硬把PrintBody()抽象方法取出來成為一個接口IBody,代碼例如以下:

abstractclass A_SeriesPrinter { //思考一下,還用abstract麽?

protected void Draw( String str) { /*實現的代碼*/ }

protected void PrintHeader() { Draw("Head");/*實現的代碼*/ }

protected void PrintFooter() { Draw("Footer");/*實現的代碼*/ }

}

interface IBody{

void PrintBody();//多了一個IBody接口的概念

}

在這裏先解決一個問題,假設Printer去掉了PrintBody()抽象方法,都是實現了的方法,是不是就應該把它定義為普通的類呢?

答案是否定的。設計一個抽象概念為抽象類的意義,不是由於它含有抽象方法。而主要由於是他表示的概念不應該被實例化,即使它裏頭的方法所有是實現了的,僅僅是想讓子類繼承的代碼。在上面這個樣例中,“A系列打印機”這個概念。是不應該有實例的。有實例的應該是詳細型號的打印機。所以,即便是所有是實現了的方法,方案二中的A_SeriesPrinter還是定義成抽象類更好。

繼續看繼承類並實現接口的代碼:

classXXPrinter extends A_SeriesPrinterimplements IBody {

public void PrintBody() { ;/*實現的代碼*/ }

public void OutReport() { // OutReport()被迫移到了實現類

PrintHeader();

PrintBody();

PrintFooter();

}

}

classYYPrinter extends A_SeriesPrinterimplements Body {

public void PrintBody() { ;/*實現的代碼*/}

public void OutReport(){ // OutReportQ被迫移到了實現類

PrintHeader();

PrintBody();

PrintFooter();

}

}

運用的代碼:

XXPrinter xx = new XXPrinterQ;

xxDutReportQ;

YYPrinter yy = new YYPrinterQ;

yy.OutReportQ;

這樣做會顯得非常奇怪和復雜: class XXPrinter extends Printer implementsIBody?好像打印Body居然是打印機的附加功能?(這太讓人難以理解了),還無端的多出了一個IBody接口的概念。

並且,OutReport()被迫移到了各個實現類,代碼變長並且復雜了。所以這時抽象類是最好的選擇。除非有業務要求須要把Body的打印從打印機分離出來。套到別的概念中去。這時才有考慮使它成為接口的可能。但再次提醒大家,代碼會變得復雜。追溯問題出現的源頭,是由於PrintBody()這個抽象方法和打印機這個概念結合的太緊密了,它本身就是打印機功能的必不可少的一部分。貪圖接口語法上的靈活性,自目的追求擴展性開放性,而不顧對問題領域的理解而建模,僅僅要某一個概念(A_SeriesPrinter)中含有的行為框架(PrintBody())都分離出來搞成接口,就會有一系列的編碼上和理解上的麻煩,反而添加了代碼的復雜性。

然而,即使在使用抽象類的場合,也不要忽視通過接口定義行為模型的原則。假設依賴於抽象類來定義行為,往往導致過於復雜的繼承關系,而通過接口定義行為可以更有效地分離行為與實現。為代碼的維護和改動帶來方便。比方我擴展A_ SeriesPrinter類。在打印後加個日誌信息,如viod outLog()方法,那麽我就不應該把它定義成A_SeriesPrinter類的抽象方法了,而是日誌接口的抽象方法。由於“日誌”這概念不屬於打印機的專有範疇。

這樣以後其它模塊用到關於日誌的操作規範時可以方便地用到這個日誌接口。

所以,關鍵在於是否能出色地結合業務要求對問題域進行理解分析。假設你沒有做好這點,你就不能建立合理的模型。這時要不就是添加編碼的復雜性,可理解性。要不就是代碼難以隨著業務擴展而維護和改動。

綜上。

1) 假設僅僅是在定義一組行為框架的話(抽象類和接口都能夠實現),抽象類合適用來定義

問題領域中的本質的抽象概念,接口合適用來定義擴展功能的抽象概念。

2) 當須要為一些相關的類提供公共的實現代碼時。應該優先考慮用抽象類來實現,由於抽

象類中的非抽象方法能夠被子類繼承下來,使實現功能的代碼更簡單

3) 當註重代碼的擴展型和可維護性時,應該優先考慮使用接口。原因有:①接口與實現它

的類之間能夠不存在不論什麽層次關系,接口能夠實現毫不相關類的同樣行為,比抽象類的使用更加方便靈活;②接口僅僅關心對象之間的交互的方法,而不關心對象所相應的詳細類。接口是程序之間的一個協議。比抽象類的使用更安全、清晰。一般使用接口的情況很多其它[1]。

簡單說的說“在僅僅是定義一組行為框架時,對於與問題域中的抽象概念的定義應該優先考慮採用抽象類。要為一些相關類提供公共的實現代碼時,應該優先考慮採用抽象類;其他情況都應該優先考慮採用接口”。——zhouyong


補充:

問題域[[5]](Problemdomain):指提問的範圍、問題之間的內在的關系和邏輯可能性空間。

在軟件project中,問題域是指被開發系統的應用領域,即在客觀世界中由該系統處理的業務範圍。



[[1]]陽小蘭 錢程 趙海廷. Java中抽象類和接口的使用研究.Software Guide, Vo1.9 No.10 Oct.2010

[[2]]百度百科:http://baike.baidu.com/view/126521.htm

[[3]]顏瑞江. Java中抽象類和接口的差別與聯系.

[[4]]劉虢俊. 淺析Java的接口和抽象類. 計算機與信息技術,2007年11月,第11期總第108期)

[[5]]http://baike.baidu.com/view/1128785.htm


Java中接口和抽象類的比較