在本小節中,我們將學習java多重繼承,比較組合和繼承。
java中的多重繼承是建立具有多個超類的單個類的功能。與其他一些流行的物件導向程式設計語言(如C++)不同,java不支援類中的多重繼承。
Java不支援一個類中的多個繼承,因為它可能產生一些問題(鑽石問題),Java有更好的方法可以實現與多重繼承相同的結果。
為了方便理解鑽石問題,首先假設java中支援多個繼承。在這種情況下,可以有一個類層次結構,如下圖所示。
假設SuperClass
是一個抽象類,宣告了一些方法。而ClassA
,ClassB
是繼承了SuperClass
類的具體類。
檔案:SuperClass.java
public abstract class SuperClass {
public abstract void doSomething();
}
檔案:ClassA.java
public class ClassA extends SuperClass{
@Override
public void doSomething(){
System.out.println("doSomething implementation of A");
}
//ClassA 自己的方法
public void methodA(){
}
}
檔案:ClassB.java
public class ClassB extends SuperClass{
@Override
public void doSomething(){
System.out.println("doSomething implementation of B");
}
//ClassB 自己的方法
public void methodB(){
}
}
現在假設ClassC
實現類似於下面的內容,它同時擴充套件了ClassA
和ClassB
,這裡範例多重繼承。
檔案:ClassC.java
// 這只是為了解釋鑽石問題的假設
// 這段程式碼不能編譯通過
public class ClassC extends ClassA, ClassB{
public void test(){
//呼叫父類別的方法
doSomething();
}
}
請注意,test()
方法中呼叫超類doSomething()
方法。這就產生了歧義,因為編譯器不知道要執行哪個超類方法。由於鑽石類圖,在java中稱為鑽石問題。Java中的鑽石問題是java不支援類中多重繼承的主要原因。
前面我們說過類中不支援多繼承,但是使用介面可以實現。單個介面可以擴充套件多個介面,下面是一個簡單的例子。
檔案:InterfaceA.java
public interface InterfaceA {
public void doSomething();
}
檔案:InterfaceB.java
public interface InterfaceB {
public void doSomething();
}
請注意,兩個介面都宣告了相同的方法,現在可以使用一個介面來擴充套件這兩個介面,如下所示。
檔案:InterfaceC.java
public interface InterfaceC extends InterfaceA, InterfaceB {
//same method is declared in InterfaceA and InterfaceB both
public void doSomething();
}
這非常好,因為介面只宣告方法,實際的實現將由實現介面的具體類完成。因此,Java介面中的多重繼承中不存在任何歧義。
這就是java類可以實現多個介面的原因,如下例所示。
檔案:InterfacesImpl.java
public class InterfacesImpl implements InterfaceA, InterfaceB, InterfaceC {
@Override
public void doSomething() {
System.out.println("doSomething implementation of concrete class");
}
public static void main(String[] args) {
InterfaceA objA = new InterfacesImpl();
InterfaceB objB = new InterfacesImpl();
InterfaceC objC = new InterfacesImpl();
//下面的所有方法呼叫都將進行相同的具體實現
objA.doSomething();
objB.doSomething();
objC.doSomething();
}
}
是否注意到每次覆蓋任何超類方法或實現任何介面方法時,都使用@Override
注釋。@Override
注釋是三個內建java註釋之一,我們應該在覆蓋任何方法時始終使用@Override
注釋。
那麼如果想在ClassC
中使用ClassA
函式methodA()
和ClassB
函式methodB()
方法,該怎麼辦? 可使用組合方案解決。下面是ClassC
的重構版本,它使用組合來使用兩個類中方法,並使用其中一個物件的doSomething()
方法。
檔案:ClassC.java
public class ClassC{
ClassA objA = new ClassA();
ClassB objB = new ClassB();
public void test(){
objA.doSomething();
}
public void methodA(){
objA.methodA();
}
public void methodB(){
objB.methodB();
}
}
Java程式設計的最佳實踐之一是「贊成組合而不是繼承」。下面將研究一些有利於這種方法的應用。
public class ClassC{
public void methodC(){
}
}
檔案:ClassD.java
public class ClassD extends ClassC{
public int test(){
return 0;
}
}
上面的程式碼編譯並且工作正常,但是如果ClassC
實現如下改變了怎麼辦:
檔案:ClassC.java
public class ClassC{
public void methodC(){
}
public void test(){
}
}
請注意,test()
方法已存在於子類中,但返回型別不同。現在ClassD
將無法編譯,如果使用任何IDE,它將建議您更改超類或子類中的返回型別。
現在想象一下,有多級類繼承和超類的情況不受控制。別無選擇,只能更改子類方法簽名或其名稱以刪除編譯錯誤。此外,將不得不在呼叫子類方法的所有地方進行更改,因此繼承會使程式碼變得脆弱。
上述問題永遠不會出現在組合中,這使得它比繼承更有利。
繼承的另一個問題是將所有超類方法暴露給用戶端,如果超類沒有正確設計並且存在安全漏洞,那麼即使完全注意實現類,也會受到糟糕實現的影響。
組合有助於提供對超類方法的受控存取,而繼承不提供對超類方法的任何控制,這也是組合優於繼承的主要優點之一。
組合的另一個好處是它提供了呼叫方法的靈活性。上面的ClassC
實現並不是最優的,它提供了與將被呼叫的方法的編譯時繫結,只需極少的更改,就可以使方法呼叫靈活並使其動態化。
檔案:ClassC.java
public class ClassC{
SuperClass obj = null;
public ClassC(SuperClass o){
this.obj = o;
}
public void test(){
obj.doSomething();
}
public static void main(String args[]){
ClassC obj1 = new ClassC(new ClassA());
ClassC obj2 = new ClassC(new ClassB());
obj1.test();
obj2.test();
}
}
執行上面範例程式碼,得到以下結果 -
doSomething implementation of A
doSomething implementation of B
方法呼叫的這種靈活性在繼承中不可用,並且提升了最佳實踐以支援組合而不是繼承。