接口只是比抽象類“更純”的一種形式。它的用途並不止那些。由於接口根本沒有具體的實施細節——也就是說,沒有與存儲空間與“接口”關聯在一起——所以沒有任何辦法可以防止多個接口合並到一起。這一點是至關重要的,因為我們經常都需要表達這樣一個意思:“x從屬於a,也從屬於b,也從屬於c”。在C++中,將多個類合並到一起的行動稱作“多重繼承”,而且操作較為不便,因為每個類都可能有一套自己的實施細節。在Java中,我們可采取同樣的行動,但只有其中一個類擁有具體的實施細節。所以在合並多個接口的時候,C++的問題不會在Java中重演。如下所示:
在一個衍生類中,我們並不一定要擁有一個抽象或具體(沒有抽象方法)的基礎類。如果確實想從一個非接口繼承,那麼只能從一個繼承。剩余的所有基本元素都必須是“接口”。我們將所有接口名置於implements關鍵字的後面,並用逗號分隔它們。可根據需要使用多個接口,而且每個接口都會成為一個獨立的類型,可對其進行上溯造型。下面這個例子展示了一個“具體”類同幾個接口合並的情況,它最終生成了一個新類:
//: Adventure.java // Multiple interfaces import java.util.*; interface CanFight { void fight(); } interface CanSwim { void swim(); } interface CanFly { void fly(); } class ActionCharacter { public void fight() {} } class Hero extends ActionCharacter implements CanFight, CanSwim, CanFly { public void swim() {} public void fly() {} } public class Adventure { static void t(CanFight x) { x.fight(); } static void u(CanSwim x) { x.swim(); } static void v(CanFly x) { x.fly(); } static void w(ActionCharacter x) { x.fight(); } public static void main(String[] args) { Hero i = new Hero(); t(i); // Treat it as a CanFight u(i); // Treat it as a CanSwim v(i); // Treat it as a CanFly w(i); // Treat it as an ActionCharacter } } ///:~
從中可以看到,Hero將具體類ActionCharacter同接口CanFight,CanSwim以及CanFly合並起來。按這種形式合並一個具體類與接口的時候,具體類必須首先出現,然後才是接口(否則編譯器會報錯)。
請注意fight()的簽名在CanFight接口與ActionCharacter類中是相同的,而且沒有在Hero中為fight()提供一個具體的定義。接口的規則是:我們可以從它繼承(稍後就會看到),但這樣得到的將是另一個接口。如果想創建新類型的一個對象,它就必須是已提供所有定義的一個類。盡管Hero沒有為fight()明確地提供一個定義,但定義是隨同ActionCharacter來的,所以這個定義會自動提供,我們可以創建Hero的對象。
在類Adventure中,我們可看到共有四個方法,它們將不同的接口和具體類作為自己的自變量使用。創建一個Hero對象後,它可以傳遞給這些方法中的任何一個。這意味著它們會依次上溯造型到每一個接口。由於接口是用Java設計的,所以這樣做不會有任何問題,而且程序員不必對此加以任何特別的關注。
注意上述例子已向我們揭示了接口最關鍵的作用,也是使用接口最重要的一個原因:能上溯造型至多個基礎類。使用接口的第二個原因與使用抽象基礎類的原因是一樣的:防止客戶程序員制作這個類的一個對象,以及規定它僅僅是一個接口。這樣便帶來了一個問題:到底應該使用一個接口還是一個抽象類呢?若使用接口,我們可以同時獲得抽象類以及接口的好處。所以假如想創建的基礎類沒有任何方法定義或者成員變量,那麼無論如何都願意使用接口,而不要選擇抽象類。事實上,如果事先知道某種東西會成為基礎類,那麼第一個選擇就是把它變成一個接口。只有在必須使用方法定義或者成員變量的時候,才應考慮采用抽象類。