你應該知道接口是一種契約,它與實現方式無關
但是類,即使是抽象類,你都能自定義成員變量,而成員變量往往就與實現方式有關。
這一點的實際意義不大。
但是有一點,類會暴露太多不必要,甚至不能暴露的東西,你看一下java.util中,大部分的數據結構,都被設計成了接口-抽象類-最後實際類
例如Collection-List
L-AbstractCollection
L-AbstractList
L-ArrayList
但是有一個,由於歷史原因,被設計成了類,比如Stack extends Vector,
你應該知道Stack的數據訪問模式,就只能是LIFO,但是Vector是一個List,可以隨機訪問,可以任意增減。
結果Stack s = new Stack();
不光能夠pop/push/peer還能add,get,set,remove
如果你用一個接口IStack 裡面只有pop/push/peer方法,然後仍然通過繼承Vector的方式實現,
IStack s = new MyStack();
此時,就無法add,get,set,remove
舉個生動一點的例子
public interface BritishSpy { public String speak(); //英國間諜講英語 } public interface GermanSpy { public String sprechen(); //德國間諜講德語 } public class DoubleAgent implements BritishSpy, GermanSpy { public String speak() { return "Hello"; } public String sprechen() { return "Gutentag"; } } // hovertree.com public class Agency { public static void toMI5(BritishSpy spy) { //軍情5處當然只能說英語,做英國間諜 spy.speak(); //spy.sprechen();不可見 } public static void inGermany(GermanSpy spy) { //spy.speak();不可見 spy.sprechen(); } public static void main(String[] args) { DoubleAgent da = new DoubleAgent(); BritishSpy es = (BritishSpy) da; GermanSpy gs = (GermanSpy) da; toMI5(da); //MI5也不知道他是一個雙重間諜,只知道他是BritishSpy toMI5(es); //更安全 何問起 //toMI5(gs); 不可能 inGermany(da); //在德國還是安全的,德國人不知道他的雙重間諜身份,只知道他是GermanSpy inGermany(gs); //inGermany(es); 不可能 } }
假設你只用class,因為不能多重繼承,所以,speak()/sprechen()比然聲明在同一個class裡面
public abstract class DoubleAgent extends Spy/**(略...)*/ {
public abstract String speak();
public abstract String sprechen();
}
public class PoorDoubleAgent {
public String speak() { return "Hello"; }
public String sprechen() { return "Gutentag"; }
}
晚了,不管你PoorDoubleAgent a = new PoorDoubleAgent();還是DoubleAgent a = new PoorDoubleAgent();,全世界都知道他是一個雙重間諜,他到哪裡都必死無疑
前面舉了一個關於“安全性”方面的例子
接口只暴露給對方(比如Agent的軍情5處方法)它所需要的足夠信息,其他無關的,甚至有害的信息不會暴露給對方。因為,我傳給你的是接口類型,我除了是這個接口(和這個接口的父接口,inteface A extends B, C)的實例外,你頂多知道我是一個Object(不是int:P),其他的姓甚名誰,哪裡住址,父母安好,兄妹幾何都與你無關,我們只需要關心我們簽訂的合同(接口)
再舉一個有關靈活性方面的例子
假設某公司已經有一個更新過N代的,邏輯復雜無比
public class A extends B /** where b extends c, c extends d and so on... */ {
public void init() {...}
public void release() {...}
public String doXXX() {...}
public String doYYY() {...}
}
而這個A又被很多類繼承或使用,doXXX/doYYY 方法已經無法更改
假設現在這個公司要參加某個標准化組織,而這個組織要求所有提供這樣的方法
String getXXX(); String getYYY();
加入用接口標准化組織只要規定成員們都實現
public interface IBusiness {
String getXXX();
String getYYY();
}
而這個公司只需要稍微改寫一點點即可
public class A extends B /** where b extends c, c extends d and so on... */
implements IBusiness {
public String getXXX() { return doXXX(); }
public String getYYY() { return doYYY(); }
//保留
public void init() {...}
public void release() {...}
public String doXXX() {...}
public String doYYY() {...}
}
這樣既滿足了標准化的要求,又滿足了無需修改原來繼承A或者使用A的無數個class(有些可能在用戶那裡,不可能更改)
假如不用接口,你有兩個選擇:數典忘祖或者自絕於人
數典忘祖:
你的新A必須繼承標准化組織的Business,原來a,b, c d...裡面的代碼全部得重新組織到這個新的A裡面,與此同時,那些調用或者繼承A的class難保不需要重寫
自絕於人
原來的就讓它去,誰也別再提它了,我們以後就用新的NewA,結果,你的新客戶倒是滿足了標准化,你的老客戶可就 :< :@ :$,而且以後需要維護A和NewA
定義接口:
interface Fight{
void fight();
}
肥肥和瘦瘦去實現這個接口:
class FatFat implements Fight{
public void fight(){
System.out.println("FatFat 打人很痛!");
}
}
class ThinThin implements Fight{
public void fight(){
System.out.println("ThinThin 打人一點都不痛!!哈哈。");
}
}
然後你可能會這另一個類中使用到FatFat和ThinThin的對象,然後都去執行fight,但是你可能不到運行時就不會知道是具體的那個類的對象,這是你就感謝他們都實現了Fight接口,你可以向上轉型,然後通過運行時的多態產生你想要的行為。http://hovertree.com/menu/java/
Fight a=new FatFat();
Fight b=new ThinThin();
a.fight();
b.fight();
這樣就會執行不同的動作。
或者如果你有一個方法
f(Fight i){
i.fight();
}
如果c是實現了Fight接口的其中一個類,那麼你就可以這樣使用這個方法:
f(c);
你不需要知道c究竟是什麼對象(不管是FatFat還是ThinThin),你都可以得到你想要的fight動作