簡單地說:接口的作用就是把使用接口的人和實現(xiàn)接口的人分開,實現(xiàn)接口的人不必要關(guān)心誰去使用,而使用接口的人也不用關(guān)心實現(xiàn)的細(xì)節(jié)。
4點關(guān)于JAVA中接口存在的意義:
1、重要性:在Java語言中, abstract class 和interface 是支持抽象類定義的兩種機制。正是由于這兩種機制的存在,才賦予了Java強大的 面向?qū)ο竽芰Α?/p>
2、簡單、規(guī)范性:如果一個項目比較龐大,那么就需要一個能理清所有業(yè)務(wù)的架構(gòu)師來定義一些主要的接口,這些接口不僅告訴開發(fā)人員你需要實現(xiàn)那些業(yè)務(wù),而且也將命名規(guī)范限制住了(防止一些開發(fā)人員隨便命名導(dǎo)致別的程序員無法看明白)。
3、維護、拓展性:比如有一個類,實現(xiàn)了某個功能,突然有一天,發(fā)現(xiàn)這個類滿足不了需求了,然后又要重新設(shè)計這個類,更糟糕是你可能要放棄這個類,那么其他地方可能有引用他,這樣修改起來很麻煩。
? ? ?如果一開始定義一個接口,把功能放在接口里,然后定義類時實現(xiàn)這個接口,然后只要用這個接口去引用實現(xiàn)它的類就行了,以后要換的話只不過是引用另一個類而已,這樣就達(dá)到維護、拓展的方便性。比如有個method1的方法,如果用接口,【接口名】 【對象名】=new 【實現(xiàn)接口的類】,這樣想用哪個類的對象就可以new哪個對象了,new a();就是用a的方法,new b()就是用b的方法,就和USB接口一樣,插什么讀什么,就是這個原理。
你要做一個畫板程序,其中里面有一個面板類,主要負(fù)責(zé)繪畫功能,然后你就這樣定義了這個類。
4、安全、嚴(yán)密性:接口是實現(xiàn)軟件松耦合的重要手段,它描敘了系統(tǒng)對外的所有服務(wù),而不涉及任何具體的實現(xiàn)細(xì)節(jié)。這樣就比較安全、嚴(yán)密一些(一般軟件服務(wù)商考慮的比較多,jdk中很多方法就是實現(xiàn)了某個接口)。
一. 對接口的三個疑問
很多初學(xué)者都大概清楚interface是1個什么, 我們可以定義1個接口, 然后在里面定義一兩個常量(static final) 或抽象方法.
然后以后寫的類就可以實現(xiàn)這個接口, 重寫里面的抽象方法.?
很多人說接口通常跟多態(tài)性一起存在.
接口的用法跟抽象類有點類似.
但是為何要這么做呢.
1.為什么不直接在類里面寫對應(yīng)的方法,? 而要多寫1個接口(或抽象類)?
2.既然接口跟抽象類差不多, 什么情況下要用接口而不是抽象類.
3. 為什么interface叫做接口呢? 跟一般范疇的接口例如usb接口, 顯卡接口有什么聯(lián)系呢?
二. 接口引用可以指向?qū)崿F(xiàn)該接口的對象
我們清楚接口是不可以被實例化, 但是接口引用可以指向1個實現(xiàn)該接口的對象.
也就是說.
假如類A impletments 了接口B
那么下面是合法的:
B b = new A();
也可以把A的對象強制轉(zhuǎn)換為 接口B的對象
A a = new A90;
B b = (B)a;
這個特性是下面內(nèi)容的前提.
三. 抽象類為了多態(tài)的實現(xiàn).
第1個答案十分簡單, 就是為了實現(xiàn)多態(tài).
下面用詳細(xì)代碼舉1個例子.
先定義幾個類,
動物(Animal) 抽象類
爬行動物(Reptile) 抽象類? 繼承動物類
哺乳動物(Mammal) 抽象類 繼承動物類
山羊(Goat) 繼承哺乳動物類
老虎(Tiger)? 繼承哺乳動物類
兔子(Rabbit) 繼承哺乳動物類
蛇(Snake)?? 繼承爬行動物類
農(nóng)夫(Farmer)?? 沒有繼承任何類 但是農(nóng)夫可以給Animal喂水(依賴關(guān)系)
它們的關(guān)系如下圖:
3.1 Animal類
這個是抽象類, 顯示也沒有"動物" 這種實體
類里面包含3個抽象方法.
1. 靜態(tài)方法getName()
2. 移動方法move(), 因為動物都能移動.? 但是各種動物有不同的移動方法, 例如老虎和山羊會跑著移動, 兔子跳著移動, 蛇會爬著移動.
作為抽象基類, 我們不關(guān)心繼承的實體類是如何移動的, 所以移動方法move()是1個抽象方法.? 這個就是多態(tài)的思想.
3. 喝水方法drink(), 同樣, 各種動物有各種飲水方法. 這個也是抽象方法.
代碼:
abstract class Animal{
? ? public abstract String getName();
? ? public abstract void move(String destination);
? ? public abstract void drink();
}
3.2 Mammal類
這個是繼承動物類的哺乳動物類, 后面的老虎山羊等都繼承自這個類.
Mammal類自然繼承了Animal類的3個抽象方法, 實體類不再用寫其他代碼.
abstract class Mammal extends Animal{
}
3.3 Reptile類
這個是代表爬行動物的抽象類, 同上, 都是繼承自Animal類.
abstract class Reptile extends Animal{
}
3.4 Tiger類
老虎類就是1個實體類, 所以它必須重寫所有繼承自超類的抽象方法, 至于那些方法如何重寫, 則取決于老虎類本身.
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.");
? ? }
}
如上, 老虎的移動方法很普通, 低頭喝水.
3.5 Goat類 和 Rabbit類
這個兩個類與Tiger類似, 它們都繼承自Mammal這個類.
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.");
? ? }
}
兔子: 喝水方法有點區(qū)別
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.");
? ? }
}
3.6 Snake類
蛇類繼承自Reptile(爬行動物)
移動方法和喝水方法都跟其他3動物有點區(qū)別.
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.");
? ? }
}
3.7 Farmer 類
Farmer類不屬于 Animal類族, 但是Farmer農(nóng)夫可以給各種動物, 喂水.
Farmer類有2個關(guān)鍵方法, 分別是
bringWater(String destination)??? -> 把水帶到某個地點
另1個就是feedWater了,?
feedWater這個方法分為三步:
首先是農(nóng)夫帶水到飼養(yǎng)室,(bringWater())
接著被喂水動物走到飼養(yǎng)室,(move())
接著動物喝水(drink())
Farmer可以給老虎喂水, 可以給山羊喂水, 還可以給蛇喂水, 那么feedWater()里的參數(shù)類型到底是老虎,山羊還是蛇呢.
實際上因為老虎,山羊, 蛇都繼承自Animal這個類, 所以feedWater里的參數(shù)類型設(shè)為Animal就可以了.
Farmer類首先叼用bringWater("飼養(yǎng)室"),
至于這個動物是如何走到飼養(yǎng)室和如何喝水的, Farmer類則不用關(guān)心.
因為執(zhí)行時, Animal超類會根據(jù)引用指向的對象類型不同 而 指向不同的被重寫的方法.? 這個就是多態(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();
? ? }
}
3.7 執(zhí)行農(nóng)夫喂水的代碼.
下面的代碼是1個農(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ān)心老虎, 蛇, 山羊它們是如何過來的. 它們?nèi)绾魏人? 這些農(nóng)夫都不必關(guān)心.
只需要調(diào)用同1個方法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() 方法不是重寫自同1個抽象方法的話, 多態(tài)就不能實現(xiàn).
農(nóng)夫類就可能要根據(jù)參數(shù)類型的不同而重載很多個? feedWater()方法了.
而且每增加1個類(例如 獅子Lion)
就需要在農(nóng)夫類里增加1個feedWater的重載方法 feedWater(Lion l)...
而接口跟抽象類類似,
這個就回答了不本文第一個問題.
1.為什么不直接在類里面寫對應(yīng)的方法,? 而要多寫1個接口(或抽象類)?
四. 抽象類解決不了的問題.
既然抽象類很好地實現(xiàn)了多態(tài)性, 那么什么情況下用接口會更加好呢?
對于上面的例子, 我們加一點需求.
Farmer 農(nóng)夫多了1個技能, 就是給另1個動物喂兔子(囧).
BringAnimal(Animal a, String destination)???? 把兔子帶到某個地點...
feedAnimal(Animal ht, Animal a)??????????? 把動物a丟給動物ht
注意農(nóng)夫并沒有把兔子宰了, 而是把小動物(a)丟給另1個被喂食的動物(ht).
那么問題來了, 那個動物必須有捕獵這個技能.? 也就是我們要給被喂食的動物加上1個方法(捕獵) hunt(Animal a).
但是現(xiàn)實上不是所有動物都有捕獵這個技能的, 所以我們不應(yīng)該把hunt(Animal a)方法加在Goat類和Rabbit類里,? 只加在Tiger類和Snake類里.
而且老虎跟蛇的捕獵方法也不一樣, 則表明hunt()的方法體在Tiger類里和Snake類里是不一樣的.
下面有3個方案.
1. 分別在Tiger類里和Snake類里加上Hunt() 方法.? 其它類(例如Goat) 不加.
2. 在基類Animal里加上Hunt()抽象方法. 在Tiger里和Snake里重寫這個Hunt() 方法.
3. 添加肉食性動物這個抽象類.???
先來說第1種方案.
這種情況下, Tiger里的Hunt(Animal a)方法與 Snake里的Hunt(Animal a)方法毫無關(guān)聯(lián). 也就是說不能利用多態(tài)性.
導(dǎo)致Farm類里的feedAnimal()方法需要分別為Tiger 與 Snake類重載. 否決.
第2種方案:
如果在抽象類Animal里加上Hunt()方法, 則所有它的非抽象派生類都要重寫實現(xiàn)這個方法, 包括 Goat類和 Rabbit類.
這是不合理的, 因為Goat類根本沒必要用到Hunt()方法, 造成了資源(內(nèi)存)浪費.
第3種方案:
加入我們在哺乳類動物下做個分叉, 加上肉食性哺乳類動物, 非肉食性哺乳動物這兩個抽象類?
首先,
肉食性這種分叉并不準(zhǔn)確, 例如很多腐蝕性動物不會捕獵, 但是它們是肉食性.
其次
這種方案會另類族圖越來越復(fù)雜, 假如以后再需要辨別能否飛的動物呢, 增加飛翔 fly()這個方法呢? 是不是還要分叉?
再次,
很現(xiàn)實的問題, 在項目中, 你很可能沒機會修改上層的類代碼, 因為它們是用Jar包發(fā)布的, 或者你沒有修改權(quán)限.
這種情況下就需要用到接口了.
五.接口與多態(tài) 以及 多繼承性.
上面的問題, 抽象類解決不了, 根本問題是Java的類不能多繼承.
因為Tiger類繼承了動物Animal類的特性(例如 move() 和 drink()) , 但是嚴(yán)格上來將 捕獵(hunt())并不算是動物的特性之一. 有些植物, 單細(xì)胞生物也會捕獵的.
所以Tiger要從別的地方來繼承Hunt()這個方法.? 接口就發(fā)揮作用了.
修改后的UML圖如下:
5.1 Huntable接口
由UML圖可知,
我們增加了1個Huntable接口.
接口里有1個方法hunt(Animal a), 就是捕捉動物, 至于怎樣捕捉則由實現(xiàn)接口的類自己決定.
代碼:
interface Huntable{
? ? public void hunt(Animal a);
}
5.2 Tiger 類
既然定義了1個Huntable(可以捕獵的)接口.
Tiger類就要實現(xiàn)這個接口并重寫接口里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");
? ? }
}
5.3 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");
? ? }
}
可見同樣實現(xiàn)接口的hunt()方法, 但是蛇與老虎的捕獵方法是有區(qū)別的.
5.4 Farmer類
這樣的話. Farmer類里的feedAnimal(Animal ht, Animal a)就可以實現(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;
本文一開始講過了, 接口的引用可以指向?qū)崿F(xiàn)該接口的對象.
當(dāng)然, 如果把Goat對象傳入Farmer的feedAnimal()里就會有異常, 因為Goat類沒有實現(xiàn)該接口. 上面那個代碼執(zhí)行失敗.
如果要避免上面的問題.
可以修改feedAnimal方法:
? ? public void feedAnimal(Huntable hab, Animal a){
? ? ? ? this.bringAnimal(a,"Feeding Room");
? ? ? ? Animal ht = (Animal)hab;
? ? ? ? ht.move("Feeding Room");
? ? ? ? hab.hunt(a);
? ? }
這樣的話, 傳入的對象就必須是實現(xiàn)了Huntable的對象, 如果把Goat放入就回編譯報錯.
但是里面一樣有一句強制轉(zhuǎn)換
Animal ht = (Animal)hab
反而更加不安全, 因為實現(xiàn)的Huntable的接口的類不一定都是Animal的派生類.
相反, 接口的出現(xiàn)就是鼓勵多種不同的類實現(xiàn)同樣的功能(方法)
例如,假如一個機械類也可以實現(xiàn)這個接口, 那么那個機械就可以幫忙打獵了(囧)
1個植物類(例如捕蠅草),實現(xiàn)這個接口, 也可以捕獵蒼蠅了.
也就是說, 接口不會限制實現(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的方法, 還繼承(實現(xiàn))了接口Huntable的方法, 一定程度上彌補java的class不支持多繼承的特點.
六.接口上應(yīng)用泛型.
上面的Huntable里還是有點限制的,
就是它里面的hunt()方法的參數(shù)是 Animal a, 也就是說這個這個接口只能用于捕獵動物.
但是在java的世界里, 接口里的方法(行為)大多數(shù)是與類的類型無關(guān)的.
也就是說, Huntable接口里的hunt()方法里不單只可以捕獵動物, 還可以捕獵其他東西(例如 捕獵植物... 敵方機械等)
6.1 Huntable接口
首先要在Huntable接口上添加泛型標(biāo)志:<T>
interface Huntable<T>{
? ? public void hunt(T o);
}
然后里面的hunt()的參數(shù)的類型就寫成T, 表示hunt()方法可以接受多種參數(shù), 取決于實現(xiàn)接口的類.
6.2 Tiger類(和Snake類)
同樣, 定義tiger類時必須加上接口的泛型標(biāo)志<Animal>, 表示要把接口應(yīng)用在Animal這種類型.
class Tiger extends Mammal implements Huntable<Animal>{
? ? 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");
? ? }
}
這樣, 在里面hunt()參數(shù)就可以指明類型Animal了,? 表示老虎雖然有捕獵這個行為, 但是只能捕獵動物.
七.什么情況下應(yīng)該使用接口而不用抽象類.
好了, 回到本文最重要的一個問題.
做個總結(jié)
1. 需要實現(xiàn)多態(tài)
2. 要實現(xiàn)的方法(功能)不是當(dāng)前類族的必要(屬性).
3. 要為不同類族的多個類實現(xiàn)同樣的方法(功能).
下面是分析:
7.1 需要實現(xiàn)多態(tài)
很明顯, 接口其中一個存在意義就是為了實現(xiàn)多態(tài). 這里不多說了.
而抽象類(繼承) 也可以實現(xiàn)多態(tài)
7.2. 要實現(xiàn)的方法(功能)不是當(dāng)前類族的必要(屬性).
上面的例子就表明, 捕獵這個方法不是動物這個類必須的,
在動物的派生類中, 有些類需要, 有些不需要.??
如果把捕獵方法卸載動物超類里面是不合理的浪費資源.
所以把捕獵這個方法封裝成1個接口, 讓派生類自己去選擇實現(xiàn)!
7.3. 要為不同類族的多個類實現(xiàn)同樣的方法(功能).
上面說過了, 其實不是只有Animal類的派生類才可以實現(xiàn)Huntable接口.
如果Farmer實現(xiàn)了這個接口, 那么農(nóng)夫自己就可以去捕獵動物了...
我們拿另個常用的接口Comparable來做例子.
這個接口是應(yīng)用了泛型,
首先, 比較(CompareTo) 這種行為很難界定適用的類族, 實際上, 幾乎所有的類都可以比較.
比如 數(shù)字類可以比較大小,?? 人類可以比較財富,? 動物可以比較體重等.
所以各種類都可以實現(xiàn)這個比較接口.
一旦實現(xiàn)了這個比較接口. 就可以開啟另1個隱藏技能:
就是可以利用Arrays.sort()來進(jìn)行排序了.
就如實現(xiàn)了捕獵的動物,
可以被農(nóng)夫Farmer喂兔子一樣...
八.接口為什么會被叫做接口, 跟真正的接口例如usb接口有聯(lián)系嗎?
對啊, 為什么叫接口, 而不叫插件(plugin)呢,? 貌似java接口的功能更類似1個插件啊.
插上某個插件, 就有某個功能啊.
實際上, 插件與接口是相輔相成的.
例如有1個外部存儲插件(U盤), 也需要使用設(shè)備具有usb接口才能使用啊.
再舉個具體的例子.
個人電腦是由大型機發(fā)展而來的
大型機->小型機->微機(PC)
而筆記本是繼承自微機的.
那么問題來了.
對于, 計算機的CPU/內(nèi)存/主板/獨顯/光驅(qū)/打印機 有很多功能(方法/行為), 那么到底哪些東西是繼承, 哪些東西是接口呢.
首先,? cpu/內(nèi)存/主板 是從大型機開始都必備的, 任何計算機都不能把它們?nèi)サ?
所以, 這三樣?xùn)|西是繼承的, 也就說筆記本的cpu/內(nèi)存/主板是繼承自微機(PC)的
但是/光驅(qū)/呢,??? 現(xiàn)實上很多超薄筆記本不需要光驅(qū)的功能.
如果光驅(qū)做成繼承, 那么筆記本就必須具有光驅(qū), 然后屏蔽光驅(qū)功能, 那么這臺筆記本還能做到超薄嗎? 浪費了資源.
所以光驅(qū),打印機這些東西就應(yīng)該做成插件.
然后, 在筆記本上做1個可以插光驅(qū)和打印機的接口(usb接口).
也就是說, PC的派生類, 有些(筆記本)可以不實現(xiàn)這個接口, 有些(臺式機)可以實現(xiàn)這個接口,只需要把光驅(qū)插到這個接口上.
至于光驅(qū)是如何實現(xiàn)的,
例如一些pc派生類選擇實現(xiàn)藍(lán)光光驅(qū), 有些選擇刻錄機.? 但是usb接口本身并不關(guān)心. 取決與實現(xiàn)接口的類.
這個就是現(xiàn)實意義上的多態(tài)性啊.