Java中的多重繼承
多重繼承可讓您建立從多個超類別繼承的類別。與其他一些流行的物件導向程式語言(例如 C++)不同,Java 不允許從類別進行多重繼承。Java 不支援多類別繼承,因為它可能導致鑽石問題。我們不需要尋找解決這個問題的方法,而是有更好的選擇來實現相同的結果,例如多重繼承。鑽石問題
為了更容易理解鑽石問題,我們假設 Java 支持多重繼承。在這種情況下,我們可以有一個類別層次結構,如下圖所示。 我們假設該類別SuperClass
是抽象的,並且在其中聲明了一些方法。具體類ClassA
和ClassB
.
package com.journaldev.inheritance;
public abstract class SuperClass {
public abstract void doSomething();
}
package com.journaldev.inheritance;
public class ClassA extends SuperClass{
@Override
public void doSomething(){
System.out.println("doSomething implementation of A");
}
//ClassA own method
public void methodA(){
}
}
package com.journaldev.inheritance;
public class ClassB extends SuperClass{
@Override
public void doSomething(){
System.out.println("doSomething implementation of B");
}
//ClassB specific method
public void methodB(){
}
}
現在假設我們要實現ClassC
並繼承它ClassA
和ClassB
。
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
public void test(){
//calling super class method
doSomething();
}
}
請注意,該方法test()
呼叫超類別方法doSomething()
。這會導致歧義,因為編譯器不知道要執行哪個超類別方法。這是一個菱形的類別圖,稱為菱形問題。這就是Java不支援多重繼承的主要原因。請注意,上述多類別繼承問題只會發生在至少有一個公共方法的三個類別中。
多介面繼承
在Java中,類別中不支援多重繼承,但介面中支援多重繼承。並且一個介面可以擴充許多其他介面。下面是一個簡單的例子。package com.journaldev.inheritance;
public interface InterfaceA {
public void doSomething();
}
package com.journaldev.inheritance;
public interface InterfaceB {
public void doSomething();
}
請注意,兩個介面聲明相同的方法。現在我們可以建立一個擴展這兩個接口的接口,如下例所示。
package com.journaldev.inheritance;
public interface InterfaceC extends InterfaceA, InterfaceB {
//same method is declared in InterfaceA and InterfaceB both
public void doSomething();
}
這非常有效,因為介面僅聲明方法,並且實作將在繼承該介面的類別中完成。因此,在多重介面繼承中沒有辦法產生歧義。
package com.journaldev.inheritance;
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();
//all the method calls below are going to same concrete implementation
objA.doSomething();
objB.doSomething();
objC.doSomething();
}
}
請注意,每當您重寫任何超類別方法或實作介面方法時,請使用註解@Override
。如果我們想在類別中使用methodA()
類別的函數和類別的ClassA
函數怎麼辦?解決方案在於使用組合。下面是該類別的一個版本,它使用組合來定義類別的方法和其中一個物件的方法。 methodB()
ClassB
ClassC
ClassC
doSomething()
package com.journaldev.inheritance;
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 程式設計實踐之一是「先批准組合再繼承」。我們將探討有利於這種方法的一些面向。-
假設我們有一個超類別和一個擴展它的類別:
package com.journaldev.inheritance; public class ClassC{ public void methodC(){ } } package com.journaldev.inheritance; public class ClassD extends ClassC{ public int test(){ return 0; } }
上面的程式碼可以編譯並且運作良好。但是,如果我們改變類別的實現,
ClassC
如下所示:package com.journaldev.inheritance; public class ClassC{ public void methodC(){ } public void test(){ } }
注意該方法
test()
已經存在於子類別中,但返回類型不同。現在該類別ClassD
將無法編譯,如果您使用任何 IDE,它會提示您變更超類別或子類別中的返回類型。現在想像一下這樣一種情況,我們有一個多層級類別繼承層次結構,並且沒有權限存取超類別。我們別無選擇,只能更改子類別方法簽章或其名稱來消除編譯錯誤。我們還必須在所有呼叫子類別方法的地方更改它。因此,繼承使我們的程式碼變得脆弱。
上述問題在組合中永遠不會發生,這使得繼承更具吸引力。
-
繼承的另一個問題是,我們將超類別的所有方法暴露給客戶端,如果我們的超類別設計不正確並且存在安全漏洞,那麼即使我們實現了類別的最佳實現,我們也會受到不良實現的影響超類別的。組合幫助我們提供對超類別方法的受控訪問,而繼承不提供對超類別方法的控制。這也是繼承組合的主要好處之一。
-
組合的另一個優點是它允許靈活地呼叫方法。我們對上面提供的類別的實作
ClassC
並不是最佳的,並且確保編譯時間與將要呼叫的方法相關聯。透過最小的改變,我們可以使方法呼叫變得靈活和動態。package com.journaldev.inheritance; 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
繼承無法提供方法呼叫的靈活性,這為組合選擇帶來了另一個好處。
-
使用組合更容易進行單元測試,因為我們知道我們正在使用超類別中的所有方法,並且可以複製它們進行測試。而在繼承中,我們更依賴超類,並且不知道將使用的超類別的所有方法。所以我們必須測試超類別的所有方法,由於繼承,這是額外的工作。
理想情況下,只有當子類別與超類別的關係定義為「is」時,我們才應該使用繼承。在所有其他情況下,建議使用該組合物。
GO TO FULL VERSION