在本小節中,我們將學習java多重繼承,比較組合和繼承。
java中的多重繼承是創建具有多個超類的單個類的功能。與其他一些流行的面向對象編程語言(如C++)不同,java不支持類中的多重繼承。
Java不支持一個類中的多個繼承,因為它可能產生一些問題(鑽石問題),Java有更好的方法可以實現與多重繼承相同的結果。
1. 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不支持類中多重繼承的主要原因。
2. 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
注釋。
2. Java組合
那麼如果想在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();
}
}
3. 組合與繼承
Java編程的最佳實踐之一是“贊成組合而不是繼承”。下麵將研究一些有利於這種方法的應用。
- 假設有一個超類和子類如下:
檔:ClassC.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
方法調用的這種靈活性在繼承中不可用,並且提升了最佳實踐以支持組合而不是繼承。
- 單元測試中很容易組合,因為我們知道在超類中使用的所有方法,可以模擬它進行測試,而在繼承中,很大程度上依賴於超類而不知道所有超類的方法將要使用,所以需要測試超類的所有方法。