Java提高篇 —— 抽象类与接口
一、前言
?
? ? ? ?接口和內(nèi)部類為我們提供了一種將接口與實(shí)現(xiàn)分離的更加結(jié)構(gòu)化的方法。
? ? ? ?抽象類與接口是java語言中對(duì)抽象概念進(jìn)行定義的兩種機(jī)制,正是由于他們的存在才賦予java強(qiáng)大的面向?qū)ο蟮哪芰ΑK麄儍烧咧g對(duì)抽象概念的支持有很大的相似,甚至可以互換,但是也有區(qū)別。
?
二、抽象類
?
? ? ? ?我們都知道在面向?qū)ο蟮念I(lǐng)域一切都是對(duì)象,同時(shí)所有的對(duì)象都是通過類來描述的,但是并不是所有的類都是來描述對(duì)象的。如果一個(gè)類沒有足夠的信息來描述一個(gè)具體的對(duì)象,而需要其他具體的類來支撐它,那么這樣的類我們稱它為抽象類。比如new Animal(),我們都知道這個(gè)是產(chǎn)生一個(gè)動(dòng)物Animal對(duì)象,但是這個(gè)Animal具體長成什么樣子我們并不知道,它沒有一個(gè)具體動(dòng)物的概念,所以他就是一個(gè)抽象類,需要一個(gè)具體的動(dòng)物,如狗、貓來對(duì)它進(jìn)行特定的描述,我們才知道它長成啥樣。
? ? ? ?在面向?qū)ο箢I(lǐng)域由于抽象的概念在問題領(lǐng)域沒有對(duì)應(yīng)的具體概念,所以用以表征抽象概念的抽象類是不能實(shí)例化的。
? ? ? ?同時(shí),抽象類體現(xiàn)了數(shù)據(jù)抽象的思想,是實(shí)現(xiàn)多態(tài)的一種機(jī)制。它定義了一組抽象的方法,至于這組抽象方法的具體表現(xiàn)形式有派生類來實(shí)現(xiàn)。同時(shí)抽象類提供了繼承的概念,它的出發(fā)點(diǎn)就是為了繼承,否則它沒有存在的任何意義。所以說定義的抽象類一定是用來繼承的,同時(shí)在一個(gè)以抽象類為節(jié)點(diǎn)的繼承關(guān)系等級(jí)鏈中,葉子節(jié)點(diǎn)一定是具體的實(shí)現(xiàn)類。
在使用抽象類時(shí)需要注意幾點(diǎn):
? ? ? ?1、抽象類不能被實(shí)例化,實(shí)例化的工作應(yīng)該交由它的子類來完成,它只需要有一個(gè)引用即可。
???????2、抽象方法必須由子類來進(jìn)行重寫。
???????3、只要包含一個(gè)抽象方法的抽象類,該方法必須要定義成抽象類,不管是否還包含有其他方法。
???????4、抽象類中可以包含具體的方法,當(dāng)然也可以不包含抽象方法。
???????5、子類中的抽象方法不能與父類的抽象方法同名。
???????6、abstract不能與final并列修飾同一個(gè)類。
???????7、abstract 不能與private、static、final或native并列修飾同一個(gè)方法。
?
實(shí)例:
? ? ? ?定義一個(gè)抽象動(dòng)物類Animal,提供抽象方法叫cry(),貓、狗都是動(dòng)物類的子類,由于cry()為抽象方法,所以Cat、Dog必須要實(shí)現(xiàn)cry()方法。如下:
public abstract class Animal {public abstract void cry(); }public class Cat extends Animal{@Overridepublic void cry() {System.out.println("貓叫:喵喵...");} }public class Dog extends Animal{@Overridepublic void cry() {System.out.println("狗叫:汪汪...");}}public class Test {public static void main(String[] args) {Animal a1 = new Cat();Animal a2 = new Dog();a1.cry();a2.cry();} }-------------------------------------------------------------------- Output: 貓叫:喵喵... 狗叫:汪汪...? ? ? ?創(chuàng)建抽象類和抽象方法非常有用,因?yàn)樗麄兛梢允诡惖某橄笮悦鞔_起來,并告訴用戶和編譯器打算怎樣使用他們.抽象類還是有用的重構(gòu)器,因?yàn)樗鼈兪刮覀兛梢院苋菀椎貙⒐卜椒ㄑ刂^承層次結(jié)構(gòu)向上移動(dòng)。(From:Think in java )
?
三、接口
?
? ? ? ?接口是一種比抽象類更加抽象的“類”。這里給“類”加引號(hào)是我找不到更好的詞來表示,但是我們要明確一點(diǎn)就是,接口本身就不是類,從我們不能實(shí)例化一個(gè)接口就可以看出。如new Runnable();肯定是錯(cuò)誤的,我們只能new它的實(shí)現(xiàn)類。
? ? ? ?接口是用來建立類與類之間的協(xié)議,它所提供的只是一種形式,而沒有具體的實(shí)現(xiàn)。同時(shí)實(shí)現(xiàn)該接口的實(shí)現(xiàn)類必須要實(shí)現(xiàn)該接口的所有方法,通過使用implements關(guān)鍵字,他表示該類在遵循某個(gè)或某組特定的接口,同時(shí)也表示著“interface只是它的外貌,但是現(xiàn)在需要聲明它是如何工作的”。
? ? ? ?接口是抽象類的延伸,java了保證數(shù)據(jù)安全是不能多重繼承的,也就是說繼承只能存在一個(gè)父類,但是接口不同,一個(gè)類可以同時(shí)實(shí)現(xiàn)多個(gè)接口,不管這些接口之間有沒有關(guān)系,所以接口彌補(bǔ)了抽象類不能多重繼承的缺陷,但是推薦繼承和接口共同使用,因?yàn)檫@樣既可以保證數(shù)據(jù)安全性又可以實(shí)現(xiàn)多重繼承。
在使用接口過程中需要注意如下幾個(gè)問題:
? ? ? 1、個(gè)Interface的方所有法訪問權(quán)限自動(dòng)被聲明為public。確切的說只能為public,當(dāng)然你可以顯示的聲明為protected、private,但是編譯會(huì)出錯(cuò)!
???????2、接口中可以定義“成員變量”,或者說是不可變的常量,因?yàn)榻涌谥械摹俺蓡T變量”會(huì)自動(dòng)變?yōu)闉閜ublic static final。可以通過類命名直接訪問:ImplementClass.name。
???? ? 3、接口中不存在實(shí)現(xiàn)的方法。
???????4、實(shí)現(xiàn)接口的非抽象類必須要實(shí)現(xiàn)該接口的所有方法。抽象類可以不用實(shí)現(xiàn)。
???????5、不能使用new操作符實(shí)例化一個(gè)接口,但可以聲明一個(gè)接口變量,該變量必須引用(refer to)一個(gè)實(shí)現(xiàn)該接口的類的對(duì)象。可以使用 instanceof 檢查一個(gè)對(duì)象是否實(shí)現(xiàn)了某個(gè)特定的接口。例如:if(anObject instanceof Comparable){}。
???????6、在實(shí)現(xiàn)多接口的時(shí)候一定要避免方法名的重復(fù)。
?
四、抽象類與接口的區(qū)別
?
? ? ? ?盡管抽象類和接口之間存在較大的相同點(diǎn),甚至有時(shí)候還可以互換,但這樣并不能彌補(bǔ)他們之間的差異之處。下面將從語法層次和設(shè)計(jì)層次兩個(gè)方面對(duì)抽象類和接口進(jìn)行闡述。
?
1、語法層次
? ? ? ?在語法層次,java語言對(duì)于抽象類和接口分別給出了不同的定義。下面已Demo類來說明他們之間的不同之處。
使用抽象類來實(shí)現(xiàn):
public abstract class Demo {abstract void method1();void method2(){//實(shí)現(xiàn)} }使用接口來實(shí)現(xiàn):
interface Demo {void method1();void method2(); }? ? ? ?抽象類方式中,抽象類可以擁有任意范圍的成員數(shù)據(jù),同時(shí)也可以擁有自己的非抽象方法,但是接口方式中,它僅能夠有靜態(tài)、不能修改的成員數(shù)據(jù)(但是我們一般是不會(huì)在接口中使用成員數(shù)據(jù)),同時(shí)它所有的方法都必須是抽象的。在某種程度上來說,接口是抽象類的特殊化。
? ? ? ?對(duì)子類而言,它只能繼承一個(gè)抽象類(這是java為了數(shù)據(jù)安全而考慮的),但是卻可以實(shí)現(xiàn)多個(gè)接口。
?
2、設(shè)計(jì)層次
? ? ? ?上面只是從語法層次和編程角度來區(qū)分它們之間的關(guān)系,這些都是低層次的,要真正使用好抽象類和接口,我們就必須要從較高層次來區(qū)分了。只有從設(shè)計(jì)理念的角度才能看出它們的本質(zhì)所在。一般來說他們存在如下三個(gè)不同點(diǎn):
? ? ? ?1、 抽象層次不同。抽象類是對(duì)類抽象,而接口是對(duì)行為的抽象。抽象類是對(duì)整個(gè)類整體進(jìn)行抽象,包括屬性、行為,但是接口卻是對(duì)類局部(行為)進(jìn)行抽象。
? ? ? ?2、 跨域不同。抽象類所跨域的是具有相似特點(diǎn)的類,而接口卻可以跨域不同的類。我們知道抽象類是從子類中發(fā)現(xiàn)公共部分,然后泛化成抽象類,子類繼承該父類即可,但是接口不同。實(shí)現(xiàn)它的子類可以不存在任何關(guān)系,共同之處。例如貓、狗可以抽象成一個(gè)動(dòng)物類抽象類,具備叫的方法。鳥、飛機(jī)可以實(shí)現(xiàn)飛Fly接口,具備飛的行為,這里我們總不能將鳥、飛機(jī)共用一個(gè)父類吧!所以說抽象類所體現(xiàn)的是一種繼承關(guān)系,要想使得繼承關(guān)系合理,父類和派生類之間必須存在"is-a" 關(guān)系,即父類和派生類在概念本質(zhì)上應(yīng)該是相同的。對(duì)于接口則不然,并不要求接口的實(shí)現(xiàn)者和接口定義在概念本質(zhì)上是一致的, 僅僅是實(shí)現(xiàn)了接口定義的契約而已。
? ? ? ?3、 設(shè)計(jì)層次不同。對(duì)于抽象類而言,它是自下而上來設(shè)計(jì)的,我們要先知道子類才能抽象出父類,而接口則不同,它根本就不需要知道子類的存在,只需要定義一個(gè)規(guī)則即可,至于什么子類、什么時(shí)候怎么實(shí)現(xiàn)它一概不知。比如我們只有一個(gè)貓類在這里,如果你這是就抽象成一個(gè)動(dòng)物類,是不是設(shè)計(jì)有點(diǎn)兒過度?我們起碼要有兩個(gè)動(dòng)物類,貓、狗在這里,我們?cè)诔橄笏麄兊墓餐c(diǎn)形成動(dòng)物抽象類吧!所以說抽象類往往都是通過重構(gòu)而來的!但是接口就不同,比如說飛,我們根本就不知道會(huì)有什么東西來實(shí)現(xiàn)這個(gè)飛接口,怎么實(shí)現(xiàn)也不得而知,我們要做的就是事前定義好飛的行為接口。所以說抽象類是自底向上抽象而來的,接口是自頂向下設(shè)計(jì)出來的。?
?
? ? ? ?為了更好的闡述他們之間的區(qū)別,下面將使用一個(gè)例子來說明。
? ? ? ?我們有一個(gè)Door的抽象概念,它具備兩個(gè)行為open()和close(),此時(shí)我們可以定義通過抽象類和接口來定義這個(gè)抽象概念:
抽象類:
abstract class Door{abstract void open();abstract void close(); }接口:
interface Door{void open();void close(); }? ? ? ?至于其他的具體類可以通過使用extends使用抽象類方式定義Door或者Implements使用接口方式定義Door,這里發(fā)現(xiàn)兩者并沒有什么很大的差異。
? ? ? ?但是現(xiàn)在如果我們需要門具有報(bào)警的功能,那么該如何實(shí)現(xiàn)呢?
?
解決方案一:
給Door增加一個(gè)報(bào)警方法:clarm();
abstract class Door{abstract void open();abstract void close();abstract void alarm(); }或者
interface Door{void open();void close();void alarm(); }? ? ? ?這種方法違反了面向?qū)ο笤O(shè)計(jì)中的一個(gè)核心原則 ISP (Interface Segregation Principle),在Door的定義中把Door概念本身固有的行為方法和另外一個(gè)概念"報(bào)警器"的行為方 法混在了一起。這樣引起的一個(gè)問題是那些僅僅依賴于Door這個(gè)概念的模塊會(huì)因?yàn)?#34;報(bào)警器"這個(gè)概念的改變而改變,反之依然。
?
解決方案二:
? ? ? ?既然open()、close()和alarm()屬于兩個(gè)不同的概念,那么我們依據(jù)ISP原則將它們分開定義在兩個(gè)代表兩個(gè)不同概念的抽象類里面,定義的方式有三種:
? ? ? ?1、兩個(gè)都使用抽象類來定義。
? ? ? ?2、兩個(gè)都使用接口來定義。
? ? ? ?3、一個(gè)使用抽象類定義,一個(gè)是用接口定義。
? ? ? ?由于java不支持多繼承所以第一種是不可行的。后面兩種都是可行的,但是選擇何種就反映了你對(duì)問題域本質(zhì)的理解。
? ? ? ?如果選擇第二種都是接口來定義,那么就反映了兩個(gè)問題:
? ? ? ?1、我們可能沒有理解清楚問題域,AlarmDoor在概念本質(zhì)上到底是門還報(bào)警器。
? ? ? ?2、如果我們對(duì)問題域的理解沒有問題,比如我們?cè)诜治鰰r(shí)確定了AlarmDoor在本質(zhì)上概念是一致的,那么我們?cè)谠O(shè)計(jì)時(shí)就沒有正確的反映出我們的設(shè)計(jì)意圖。因?yàn)槟闶褂昧藘蓚€(gè)接口來進(jìn)行定義,他們概念的定義并不能夠反映上述含義。
? ? ? ?第三種,如果我們對(duì)問題域的理解是這樣的:AlarmDoor本質(zhì)上Door,但同時(shí)它也擁有報(bào)警的行為功能,這個(gè)時(shí)候我們使用第三種方案恰好可以闡述我們的設(shè)計(jì)意圖。AlarmDoor本質(zhì)是們,所以對(duì)于這個(gè)概念我們使用抽象類來定義,同時(shí)AlarmDoor具備報(bào)警功能,說明它能夠完成報(bào)警概念中定義的行為功能,所以alarm可以使用接口來進(jìn)行定義。如下:
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(){} }? ? ? ?這種實(shí)現(xiàn)方式基本上能夠明確的反映出我們對(duì)于問題領(lǐng)域的理解,正確的揭示我們的設(shè)計(jì)意圖。其實(shí)抽象類表示的是"is-a"關(guān)系,接口表示的是"like-a"關(guān)系,大家在選擇時(shí)可以作為一個(gè)依據(jù),當(dāng)然這是建立在對(duì)問題領(lǐng)域的理解上的,比如:如果我們認(rèn)為AlarmDoor在概念本質(zhì)上是報(bào)警器,同時(shí)又具有Door的功能,那么上述的定義方式就要反過來了。
?
五、總結(jié)
?
? ? ? ?1、 抽象類在java語言中所表示的是一種繼承關(guān)系,一個(gè)子類只能存在一個(gè)父類,但是可以存在多個(gè)接口。
? ? ? ?2、 在抽象類中可以擁有自己的成員變量和非抽象類方法,但是接口中只能存在靜態(tài)的不可變的成員數(shù)據(jù)(不過一般都不在接口中定義成員數(shù)據(jù)),而且它的所有方法都是抽象的。
? ? ? ?3、抽象類和接口所反映的設(shè)計(jì)理念是不同的,抽象類所代表的是“is-a”的關(guān)系,而接口所代表的是“l(fā)ike-a”的關(guān)系。
抽象類和接口是java語言中兩種不同的抽象概念,他們的存在對(duì)多態(tài)提供了非常好的支持,雖然他們之間存在很大的相似性。但是對(duì)于他們的選擇往往反應(yīng)了您對(duì)問題域的理解。只有對(duì)問題域的本質(zhì)有良好的理解,才能做出正確、合理的設(shè)計(jì)。
?
?
總結(jié)
以上是生活随笔為你收集整理的Java提高篇 —— 抽象类与接口的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 支付宝etc信用卡额度是多少?90%的人
- 下一篇: 百度首席科学家 Andrew Ng谈深度