接口的特點(diǎn)和繼承
- 沒(méi)有構(gòu)造器,不能實(shí)例化
- 接口只能繼承接口,不能繼承類,且支持多繼承
- 接口里的方法全是抽象的,沒(méi)有方法體,默認(rèn)修飾符public abstract
[修飾符] interface 接口名 extends 接口1,接口2 - 接口的字段全是全局靜態(tài)變量,默認(rèn)修飾符是public static final
- 接口里的內(nèi)部類全是公共靜態(tài)的,默認(rèn)修飾符是public static
- 接口只能和接口有繼承關(guān)系,接口和類是實(shí)現(xiàn)關(guān)系
接口的實(shí)現(xiàn)關(guān)系
語(yǔ)法:
[修飾符] class 實(shí)現(xiàn)類名 extends 父類 implments 接口1,接口2
接口僅僅只是定義了某一類事務(wù)應(yīng)該具有某些功能,但沒(méi)有提供任何實(shí)現(xiàn),此時(shí)我們需要提供類,再讓類去實(shí)現(xiàn)接口,并覆蓋接口中的方法,從而實(shí)現(xiàn)類接口中定義的功能
接口和實(shí)現(xiàn)類的關(guān)系,嚴(yán)格上稱之為實(shí)現(xiàn)關(guān)系,使用implments來(lái)表示,但是在開(kāi)發(fā)中有時(shí)為了方便也把這個(gè)關(guān)系稱之為特殊繼承關(guān)系,所以可以這樣理解:接口是實(shí)現(xiàn)類的父類,實(shí)現(xiàn)類就是借口的子類
接口 變量=創(chuàng)建實(shí)現(xiàn)類的對(duì)象//體現(xiàn)了多態(tài)(用的最多的)
類和類 類和接口關(guān)系圖
[圖片上傳失敗...(image-bd6cf7-1515037121233)]
接口和抽象類的區(qū)別:
相同點(diǎn):
- 都位于繼承的頂端,用于被其他類的實(shí)現(xiàn)或繼承
- 都不能實(shí)例化
- 都可以定義抽象方法,其子類/實(shí)現(xiàn)類必須覆蓋這些抽象方法
不同點(diǎn):
- 接口沒(méi)有構(gòu)造方法,抽象類有
- 抽象類能包含普通方法和抽象方法,接口只能包含抽象方法(java8之前)
- 一個(gè)類只能繼承一個(gè)直接父類(可能是抽象類),接口是多繼承的并且只支持一個(gè)類實(shí)現(xiàn)多個(gè)接口(彌補(bǔ)了java的單繼承)
- 成員變量:接口默認(rèn)是public static final 抽象類是默認(rèn)包權(quán)限
- 方法:接口默認(rèn)是public abstract 抽象類是默認(rèn)包權(quán)限
- 內(nèi)部類:接口默認(rèn)是 public static,抽象類是默認(rèn)包權(quán)限
如果抽象類和接口都能完成相同的功能,且盡量使用接口,面向接口編程
面向接口編程思想:
//usb類 IUSB接口
interface IUSB{
void swapDate ();
}
public class usb
{
public static void main(String[] args) {
motherBoard zhuban=new motherBoard();
printer dayinji=new printer();
mouse shubiao=new mouse();
zhuban.plugIn(shubiao);
zhuban.plugIn(shubiao);
zhuban.plugIn(dayinji);
zhuban.dowork();
}
}
//主板類
public class motherBoard {
IUSB [] usbb=new IUSB[6];
int index=0;
public void plugIn(IUSB usb){
usbb[index++]=usb;
}
public void dowork(){
for(int i=0;i<index;i++)
{
usbb[i].swapDate();
}
}
//鼠標(biāo)類
public class mouse implements IUSB{
public void swapDate(){
System.out.println("鼠標(biāo)在移動(dòng)");
}
}
//打印機(jī)類
public class printer implements IUSB{
public void swapDate(){
System.out.println("打印,嘟嘟嘟嘟....");
}
}
抽象類為了多態(tài)的實(shí)現(xiàn)
第1個(gè)答案十分簡(jiǎn)單, 就是為了實(shí)現(xiàn)多態(tài).
下面用詳細(xì)代碼舉1個(gè)例子.
先定義幾個(gè)類,
動(dòng)物(Animal) 抽象類
爬行動(dòng)物(Reptile) 抽象類 繼承動(dòng)物類
哺乳動(dòng)物(Mammal) 抽象類 繼承動(dòng)物類
山羊(Goat) 繼承哺乳動(dòng)物類
老虎(Tiger) 繼承哺乳動(dòng)物類
兔子(Rabbit) 繼承哺乳動(dòng)物類
蛇(Snake) 繼承爬行動(dòng)物類
農(nóng)夫(Farmer) 沒(méi)有繼承任何類 但是農(nóng)夫可以給Animal喂水(依賴關(guān)系)
Animal類
這個(gè)是抽象類, 顯示也沒(méi)有"動(dòng)物" 這種實(shí)體
類里面包含3個(gè)抽象方法.
靜態(tài)方法getName()
移動(dòng)方法move(), 因?yàn)閯?dòng)物都能移動(dòng). 但是各種動(dòng)物有不同的移動(dòng)方法, 例如老虎和山羊會(huì)跑著移動(dòng), 兔子跳著移動(dòng), 蛇會(huì)爬著移動(dòng).
作為抽象基類, 我們不關(guān)心繼承的實(shí)體類是如何移動(dòng)的, 所以移動(dòng)方法move()是1個(gè)抽象方法. 這個(gè)就是多態(tài)的思想.
喝水方法drink(), 同樣, 各種動(dòng)物有各種飲水方法. 這個(gè)也是抽象方法.
代碼:
abstract class Animal{
public abstract String getName();
public abstract void move(String destination);
public abstract void drink();
}
Mammal類
這個(gè)是繼承動(dòng)物類的哺乳動(dòng)物類, 后面的老虎山羊等都繼承自這個(gè)類.
Mammal類自然繼承了Animal類的3個(gè)抽象方法, 實(shí)體類不再用寫(xiě)其他代碼.
abstract class Mammal extends Animal{
}
Reptile類
這個(gè)是代表爬行動(dòng)物的抽象類, 同上, 都是繼承自Animal類.
abstract class Reptile extends Animal{
}
Tiger類
老虎類就是1個(gè)實(shí)體類, 所以它必須重寫(xiě)所有繼承自超類的抽象方法, 至于那些方法如何重寫(xiě), 則取決于老虎類本身.
老虎的移動(dòng)方法很普通, 低頭喝水.
class Tiger extends Mammal{
private static String name = "Tiger";
public String getName(){
return this.name;
}
public void move(String destination){
System.out.println("Goat moved to " + destination + ".");
}
public void drink(){
System.out.println("Goat lower it's head and drink.");
}
}
Goat類 和 Rabbit類
這個(gè)兩個(gè)類與Tiger類似, 它們都繼承自Mammal這個(gè)類.
class Goat extends Mammal{
private static String name = "Goat";
public String getName(){
return this.name;
}
public void move(String destination){
System.out.println("Goat moved to " + destination + ".");
}
public void drink(){
System.out.println("Goat lower it's head and drink.");
}
}
class Rabbit extends Mammal{
private static String name = "Rabbit";
public String getName(){
return this.name;
}
public void move(String destination){
System.out.println("Rabbit moved to " + destination + ".");
}
public void drink(){
System.out.println("Rabbit put out it's tongue and drink.");
}
}
Snake類
蛇類繼承自Reptile(爬行動(dòng)物)
class Snake extends Reptile{
private static String name = "Snake";
public String getName(){
return this.name;
}
public void move(String destination){
System.out.println("Snake crawled to " + destination + ".");
}
public void drink(){
System.out.println("Snake dived into water and drink.");
}
}
Farmer 類
Farmer類不屬于 Animal類族, 但是Farmer農(nóng)夫可以給各種動(dòng)物, 喂水.
Farmer類有2個(gè)關(guān)鍵方法, 分別是
bringWater(String destination) -> 把水帶到某個(gè)地點(diǎn)
另1個(gè)就是feedWater了,
feedWater這個(gè)方法分為三步:
首先是農(nóng)夫帶水到飼養(yǎng)室,(bringWater())
接著被喂水動(dòng)物走到飼養(yǎng)室,(move())
接著動(dòng)物喝水(drink())
Farmer可以給老虎喂水, 可以給山羊喂水, 還可以給蛇喂水, 那么feedWater()里的參數(shù)類型到底是老虎,山羊還是蛇呢.
實(shí)際上因?yàn)槔匣?山羊, 蛇都繼承自Animal這個(gè)類, 所以feedWater里的參數(shù)類型設(shè)為Animal就可以了.
Farmer類首先叼用bringWater("飼養(yǎng)室"),
至于這個(gè)動(dòng)物是如何走到飼養(yǎng)室和如何喝水的, Farmer類則不用關(guān)心.
因?yàn)閳?zhí)行時(shí), Animal超類會(huì)根據(jù)引用指向的對(duì)象類型不同 而 指向不同的被重寫(xiě)的方法. 這個(gè)就是多態(tài)的意義.
class Farmer{
public void bringWater(String destination){
System.out.println("Farmer bring water to " + destination + ".");
}
public void feedWater(Animal a){ // polymorphism
this.bringWater("Feeding Room");
a.move("Feeding Room");
a.drink();
}
}
執(zhí)行農(nóng)夫喂水的代碼
下面的代碼是1個(gè)農(nóng)夫依次喂水給一只老虎, 一只羊, 以及一條蛇
public static void f(){
Farmer fm = new Farmer();
Snake sn = new Snake();
Goat gt = new Goat();
Tiger tg = new Tiger();
fm.feedWater(sn);
fm.feedWater(gt);
fm.feedWater(tg);
}
農(nóng)夫只負(fù)責(zé)帶水過(guò)去制定地點(diǎn), 而不必關(guān)心老虎, 蛇, 山羊它們是如何過(guò)來(lái)的. 它們?nèi)绾魏人? 這些農(nóng)夫都不必關(guān)心.
只需要調(diào)用同1個(gè)方法feedWater.
執(zhí)行結(jié)果:
[java] Farmer bring water to Feeding Room.
[java] Snake crawled to Feeding Room.
[java] Snake dived into water and drink.
[java] Farmer bring water to Feeding Room.
[java] Goat moved to Feeding Room.
[java] Goat lower it's head and drink.
[java] Farmer bring water to Feeding Room.
[java] Goat moved to Feeding Room.
[java] Goat lower it's head and drink.
不使用多態(tài)的后果?:
而如果老虎, 蛇, 山羊的drink() 方法不是重寫(xiě)自同1個(gè)抽象方法的話, 多態(tài)就不能實(shí)現(xiàn).
農(nóng)夫類就可能要根據(jù)參數(shù)類型的不同而重載很多個(gè) feedWater()方法了.
而且每增加1個(gè)類(例如 獅子Lion)
就需要在農(nóng)夫類里增加1個(gè)feedWater的重載方法 feedWater(Lion l)...
而接口跟抽象類類似,
這個(gè)就回答了第一個(gè)問(wèn)題.
1.為什么不直接在類里面寫(xiě)對(duì)應(yīng)的方法, 而要多寫(xiě)1個(gè)接口(或抽象類)?
抽象類解決不了的問(wèn)題
既然抽象類很好地實(shí)現(xiàn)了多態(tài)性, 那么什么情況下用接口會(huì)更加好呢?
對(duì)于上面的例子, 我們加一點(diǎn)需求.
Farmer 農(nóng)夫多了1個(gè)技能, 就是給另1個(gè)動(dòng)物喂兔子(囧).
BringAnimal(Animal a, String destination) 把兔子帶到某個(gè)地點(diǎn)...
feedAnimal(Animal ht, Animal a) 把動(dòng)物a丟給動(dòng)物ht
注意農(nóng)夫并沒(méi)有把兔子宰了, 而是把小動(dòng)物(a)丟給另1個(gè)被喂食的動(dòng)物(ht).
那么問(wèn)題來(lái)了, 那個(gè)動(dòng)物必須有捕獵這個(gè)技能. 也就是我們要給被喂食的動(dòng)物加上1個(gè)方法(捕獵) hunt(Animal a).
但是現(xiàn)實(shí)上不是所有動(dòng)物都有捕獵這個(gè)技能的, 所以我們不應(yīng)該把hunt(Animal a)方法加在Goat類和Rabbit類里, 只加在Tiger類和Snake類里.
而且老虎跟蛇的捕獵方法也不一樣, 則表明hunt()的方法體在Tiger類里和Snake類里是不一樣的.
下面有3個(gè)方案.
分別在Tiger類里和Snake類里加上Hunt() 方法. 其它類(例如Goat) 不加.
在基類Animal里加上Hunt()抽象方法. 在Tiger里和Snake里重寫(xiě)這個(gè)Hunt() 方法.
添加肉食性動(dòng)物這個(gè)抽象類.
先來(lái)說(shuō)第1種方案.
這種情況下, Tiger里的Hunt(Animal a)方法與 Snake里的Hunt(Animal a)方法毫無(wú)關(guān)聯(lián). 也就是說(shuō)不能利用多態(tài)性.
導(dǎo)致Farm類里的feedAnimal()方法需要分別為T(mén)iger 與 Snake類重載. 否決.
第2種方案:
如果在抽象類Animal里加上Hunt()方法, 則所有它的非抽象派生類都要重寫(xiě)實(shí)現(xiàn)這個(gè)方法, 包括 Goat類和 Rabbit類.
這是不合理的, 因?yàn)镚oat類根本沒(méi)必要用到Hunt()方法, 造成了資源(內(nèi)存)浪費(fèi).
第3種方案:
加入我們?cè)诓溉轭悇?dòng)物下做個(gè)分叉, 加上肉食性哺乳類動(dòng)物, 非肉食性哺乳動(dòng)物這兩個(gè)抽象類?
首先,
肉食性這種分叉并不準(zhǔn)確, 例如很多腐蝕性動(dòng)物不會(huì)捕獵, 但是它們是肉食性.
其次
這種方案會(huì)另類族圖越來(lái)越復(fù)雜, 假如以后再需要辨別能否飛的動(dòng)物呢, 增加飛翔 fly()這個(gè)方法呢? 是不是還要分叉?
再次,
很現(xiàn)實(shí)的問(wèn)題, 在項(xiàng)目中, 你很可能沒(méi)機(jī)會(huì)修改上層的類代碼, 因?yàn)樗鼈兪怯肑ar包發(fā)布的, 或者你沒(méi)有修改權(quán)限.
這種情況下就需要用到接口了.
接口與多態(tài) 以及 多繼承性.
上面的問(wèn)題, 抽象類解決不了, 根本問(wèn)題是Java的類不能多繼承.
因?yàn)門(mén)iger類繼承了動(dòng)物Animal類的特性(例如 move() 和 drink()) , 但是嚴(yán)格上來(lái)將 捕獵(hunt())并不算是動(dòng)物的特性之一. 有些植物, 單細(xì)胞生物也會(huì)捕獵的.
所以Tiger要從別的地方來(lái)繼承Hunt()這個(gè)方法. 接口就發(fā)揮作用了.
Huntable接口
我們?cè)黾恿?個(gè)Huntable接口.
接口里有1個(gè)方法hunt(Animal a), 就是捕捉動(dòng)物, 至于怎樣捕捉則由實(shí)現(xiàn)接口的類自己決定.
interface Huntable{
public void hunt(Animal a);
}
Tiger 類
既然定義了1個(gè)Huntable(可以捕獵的)接口.
Tiger類就要實(shí)現(xiàn)這個(gè)接口并重寫(xiě)接口里hunt()方法.
class Tiger extends Mammal implements Huntable{
private static String name = "Tiger";
public String getName(){
return this.name;
}
public void move(String destination){
System.out.println("Goat moved to " + destination + ".");
}
public void drink(){
System.out.println("Goat lower it's head and drink.");
}
public void hunt(Animal a){
System.out.println("Tiger catched " + a.getName() + " and eated it");
}
}
Snake類
class Snake extends Reptile implements Huntable{
private static String name = "Snake";
public String getName(){
return this.name;
}
public void move(String destination){
System.out.println("Snake crawled to " + destination + ".");
}
public void drink(){
System.out.println("Snake dived into water and drink.");
}
public void hunt(Animal a){
System.out.println("Snake coiled " + a.getName() + " and eated it");
}
}
Farmer類
這樣的話. Farmer類里的feedAnimal(Animal ht, Animal a)就可以實(shí)現(xiàn)多態(tài)了.
class Farmer{
public void bringWater(String destination){
System.out.println("Farmer bring water to " + destination + ".");
}
public void bringAnimal(Animal a,String destination){
System.out.println("Farmer bring " + a.getName() + " to " + destination + ".");
}
public void feedWater(Animal a){
this.bringWater("Feeding Room");
a.move("Feeding Room");
a.drink();
}
public void feedAnimal(Animal ht , Animal a){
this.bringAnimal(a,"Feeding Room");
ht.move("Feeding Room");
Huntable hab = (Huntable)ht;
hab.hunt(a);
}
}
關(guān)鍵是這一句
Huntable hab = (Huntable)ht;
本文一開(kāi)始講過(guò)了, 接口的引用可以指向?qū)崿F(xiàn)該接口的對(duì)象.
當(dāng)然, 如果把Goat對(duì)象傳入Farmer的feedAnimal()里就會(huì)有異常, 因?yàn)镚oat類沒(méi)有實(shí)現(xiàn)該接口. 上面那個(gè)代碼執(zhí)行失敗.
如果要避免上面的問(wèn)題.
可以修改feedAnimal方法:
public void feedAnimal(Huntable hab, Animal a){
this.bringAnimal(a,"Feeding Room");
Animal ht = (Animal)hab;
ht.move("Feeding Room");
hab.hunt(a);
}
這樣的話, 傳入的對(duì)象就必須是實(shí)現(xiàn)了Huntable的對(duì)象, 如果把Goat放入就回編譯報(bào)錯(cuò).
但是里面一樣有一句強(qiáng)制轉(zhuǎn)換
Animal ht = (Animal)hab
反而更加不安全, 因?yàn)閷?shí)現(xiàn)的Huntable的接口的類不一定都是Animal的派生類.
相反, 接口的出現(xiàn)就是鼓勵(lì)多種不同的類實(shí)現(xiàn)同樣的功能(方法)
例如,假如一個(gè)機(jī)械類也可以實(shí)現(xiàn)這個(gè)接口, 那么那個(gè)機(jī)械就可以幫忙打獵了(囧)
1個(gè)植物類(例如捕蠅草),實(shí)現(xiàn)這個(gè)接口, 也可以捕獵蒼蠅了.
也就是說(shuō), 接口不會(huì)限制實(shí)現(xiàn)接口的類的類型.
執(zhí)行輸出:
[java] Farmer bring Rabbit to Feeding Room.
[java] Snake crawled to Feeding Room.
[java] Snake coiled Rabbit and eated it
[java] Farmer bring Rabbit to Feeding Room.
[java] Goat moved to Feeding Room.
[java] Tiger catched Rabbit and eated it
這樣, Tiger類與Snake類不但繼承了Animal的方法, 還繼承(實(shí)現(xiàn))了接口Huntable的方法, 一定程度上彌補(bǔ)java的class不支持多繼承的特點(diǎn).
總結(jié) 什么情況下應(yīng)該使用接口而不用抽象類
好了, 回到本文最重要的一個(gè)問(wèn)題.
做個(gè)總結(jié)
需要實(shí)現(xiàn)多態(tài)
要實(shí)現(xiàn)的方法(功能)不是當(dāng)前類族的必要(屬性).
要為不同類族的多個(gè)類實(shí)現(xiàn)同樣的方法(功能).
下面是分析:
1 需要實(shí)現(xiàn)多態(tài)
很明顯, 接口其中一個(gè)存在意義就是為了實(shí)現(xiàn)多態(tài). 這里不多說(shuō)了.
而抽象類(繼承) 也可以實(shí)現(xiàn)多態(tài)
2 要實(shí)現(xiàn)的方法(功能)不是當(dāng)前類族的必要(屬性).
上面的例子就表明, 捕獵這個(gè)方法不是動(dòng)物這個(gè)類必須的,
在動(dòng)物的派生類中, 有些類需要, 有些不需要.
如果把捕獵方法寫(xiě)在動(dòng)物超類里面是不合理的浪費(fèi)資源.
所以把捕獵這個(gè)方法封裝成1個(gè)接口, 讓派生類自己去選擇實(shí)現(xiàn)!
3 要為不同類族的多個(gè)類實(shí)現(xiàn)同樣的方法(功能).
上面說(shuō)過(guò)了, 其實(shí)不是只有Animal類的派生類才可以實(shí)現(xiàn)Huntable接口.
如果Farmer實(shí)現(xiàn)了這個(gè)接口, 那么農(nóng)夫自己就可以去捕獵動(dòng)物了...
我們拿另個(gè)常用的接口Comparable來(lái)做例子.
這個(gè)接口是應(yīng)用了泛型,
首先, 比較(CompareTo) 這種行為很難界定適用的類族, 實(shí)際上, 幾乎所有的類都可以比較.
比如 數(shù)字類可以比較大小, 人類可以比較財(cái)富, 動(dòng)物可以比較體重等.
所以各種類都可以實(shí)現(xiàn)這個(gè)比較接口.
一旦實(shí)現(xiàn)了這個(gè)比較接口. 就可以開(kāi)啟另1個(gè)隱藏技能:
就是可以利用Arrays.sort()來(lái)進(jìn)行排序了.
就如實(shí)現(xiàn)了捕獵的動(dòng)物,
可以被農(nóng)夫Farmer喂兔子一樣...