Q:Java不支援多繼承?
A:很不幸,的確是的。幾乎任何一本教科書上都是這麼寫的:Java遵循單根繼承結構。
Q:為什麼一定要多繼承?
A:老掉牙的例子:沙發床既是沙發又是床。
Q:繼承意味著什嗎?
A:繼承意味著“is-a”關係。但這個說法不準確。很多“古老”的C++書上這麼說:繼承意味著兩種關係:is-a和like-a。
Q:like-a意味著什嗎?
A:不要忘了,對於早期那些剛從C遷移到C++的程式員,也就是剛從面向過程轉向物件導向程式設計的程式員而言,繼承最顯眼之處在於:代碼重用。有理由相信,一個C++程式員繼承了一個類,並不是因為“A is a B”,而只是因為“A 看上去和B很像,二者只有細微的差別”,通過繼承和重寫可以節省很多代碼。
事實上,即使在Java程式設計中我仍然常常這麼做。
Q:還有必要like-a嗎?
A:當物件導向的理論和語言都已經成熟到了今天這樣。我們已經有足夠的理由把like-a的繼承關係掃進垃圾堆了。通過仔細、嚴謹的設計和分析,我們可以構造出嚴格滿足is-a關係的繼承結構。例如,我們將A和B中“看上去很像”的公用部分剝離出來,作為一個抽象類別C。然後A和B分別繼承自C,這樣多好!
但很不幸這隻是理論。現實是:即使在最權威的Java程式——J2SE的標準API中,仍然存在這這種like-a的繼承關係。例如java.sql.Data類。
Q:like-a做錯了什嗎?
A:like-a的邪惡之處在於:它破壞了繼承的可替代性。
基於is-a的繼承具備可替代性,即凡是可以出現父類的地方,都能用子類來替代。因為子類“是一個”父類,所以父類能做的一切,子類都可以做。
like-a破壞了這種約定。因為like-a關係的兩端只是“看上去很像”,並不具備邏輯上的“是一個”的關係。所以不保證子類能做父類能做的任何事。
畢竟,like-a的繼承只是為了代碼重用的方便,它不保證二者邏輯上的真正關係!
Q:對介面(interface)的繼承(實現)意味著什嗎?
A:很明顯,對介面的實現一定是is-a的“繼承”。如果允許我把implements當作一種繼承的話。
理由:因為like-a繼承的目的僅是代碼重用。而介面中只有方法聲明沒有方法體,不存在代碼重用的可能性。
Q:對介面的is-a繼承和對父類的is-a繼承有區別嗎?
A:有區別。
當繼承(實現)一個介面時,我們的動機是"單純"的:繼承介面。
當繼承一個父類時,我們不僅繼承了父類的全部介面,還繼承了父類的實現。也就是說,這裡包含了代碼重用的目的,即潛在的包含了like-a的繼承。
Q:繼承介面和繼承實現,有什麼區別,有什麼關係?
A:首先,實現是對介面的實現。因此如果繼承了實現,必須先繼承了介面。
這也解釋了為什麼Java中只有單純對介面的繼承(implememnts),同時對介面和實現的繼承(extends),卻沒有單純對實現的繼承。
同時,is-a的目標是繼承介面,但可能附帶繼承可介面的實現。
like-a的目的是繼承實現(代碼重用),雖然繼承了介面,卻純屬不得已而為之。對繼承而來的介面也可能不加保護得修改破壞。這也就是為什麼like-a破壞了繼承的可替換性。
Q:這麼說,對介面的完好繼承保證了繼承的可替換性?
A:完全正確。
Q:是否會有一天,我們可以把對介面的繼承,和對實現的繼承明確區分開來?
A:我期待有一天,一門新的語言會提供這樣的功能。如果在Java上增加這一語言機制,我想可能是這樣。(注意:是語言機制,而不是通過API!)
public class A implements (interface of B){ ...}
也就是說,允許我們直接將一個類的介面從這個類中剝離出來。也許這樣,java基於介面和內部類的多繼承解決方案才會更完美