JavaRush /Java Blog /Random-TW /Java 中的多重繼承。組成和繼承的比較
HonyaSaar
等級 26
Москва

Java 中的多重繼承。組成和繼承的比較

在 Random-TW 群組發布
前段時間我寫了幾篇關於Java中的繼承、介面和組合的文章。在本文中,我們將了解多重繼承,然後了解組合相對於繼承的優勢。
Java 中的多重繼承。 組成和繼承的比較 - 1

Java中的多重繼承

多重繼承是創建具有多個父類別的類別的能力。與C++等其他流行的物件導向語言不同,Java不支援多類別繼承。他不支持它,因為可能會遇到“鑽石問題”,而是更願意提供某種綜合方法來解決它,使用我們可以實現類似繼承結果的最佳選項。

《鑽石問題》

為了更簡單地理解鑽石問題,我們假設Java支持多重繼承。在這種情況下,我們可以獲得具有如下圖所示層次結構的類別。 鑽石層次結構我們假設 SuperClass是一個描述某種方法的抽象類,並且類別 ClassAClassB是真實的類別。 SuperClass.java
package com.journaldev.inheritance;
public abstract class SuperClass {
   	public abstract void doSomething();
}
ClassA.java
package com.journaldev.inheritance;
public class ClassA extends SuperClass{
    @Override
 public void doSomething(){
        System.out.println("Какая-то реализация класса A");
    }
  //собственный метод класса  ClassA
    public void methodA(){
    }
}
現在,我們假設該類別同時 ClassC繼承自 ClassA和,並且同時具有以下實作: ClassB
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
    public void test(){
        //вызов метода родительского класса
        doSomething();
    }
}
請注意,該方法呼叫了父類別的 test()方法,這會導致歧義,因為編譯器不知道應該呼叫哪個超類別方法。 doSomething()由於這種情況下類繼承圖的形狀類似於多面鑽石的輪廓,因此該問題被稱為“鑽石問題”。這就是Java不支援多類別繼承的主要原因。請注意,多類別繼承的這一問題也可能發生在具有至少一個公共方法的三個類別中。

多重繼承和介面

你可能注意到了,我總是說“類別之間不支援多重繼承”,但介面之間是支援的。一個簡單的例子如下: InterfaceA.java
package com.journaldev.inheritance;
public interface InterfaceA {

    public void doSomething();
}
InterfaceB.java
package com.journaldev.inheritance;

public interface InterfaceB {

    public void doSomething();
}
請注意,兩個介面都有一個同名的方法。現在假設我們有一個繼承自這兩個介面的介面。 InterfaceC.java
package com.journaldev.inheritance;

public interface InterfaceC extends InterfaceA, InterfaceB {

    //метод, с тем же названием описан в  InterfaceA и InterfaceB
    public void doSomething();
在這裡,一切都是理想的,因為介面只是方法的保留/描述,而方法本身的實作將在實作這些介面的特定類別中,因此不會遇到介面多重繼承的歧義。這就是為什麼Java中的類別可以繼承多個介面。讓我們用下面的例子來展示它。 InterfacesImpl.java
package com.journaldev.inheritance;

public class InterfacesImpl implements InterfaceA, InterfaceB, InterfaceC {

    @Override
    public void doSomething() {
        System.out.println("doSomething реализация реального класса ");
    }

    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 註解。這是三個內建 Java 註解之一,在重寫方法時應始終使用它。

作曲作為救贖

那如果我們想在 中使用 methodA()類別 ClassAmethodB()類別函數呢?解決這個問題的方法可能是組合——一種重寫版本,它實現了兩個類別方法,並且還具有其中一個物件的 實作。 ClassB ClassС ClassC ClassA ClassB doSomething() ClassC.java
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 程式設計實作。我們將研究支持這種方法的一些方面。
  1. 假設我們有以下父繼承類別的組合:

    ClassC.java

    package com.journaldev.inheritance;
    
    public class ClassC{
    
    public void methodC(){
      	}
    
    }

    ClassD.java

    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,它將無法編譯。我們將建議您更改後代或超類別中的返回類型。

    現在讓我們想像一種情況,類別存在多層繼承,超類別不可用於我們的變更。現在,為了消除編譯錯誤,除了更改子類別方法的簽章或名稱之外,我們別無選擇。我們還必須更改呼叫此方法的所有位置。因此,繼承使我們的程式碼變得脆弱。

    上述問題在組合的情況下不會出現,因此組合比繼承更可取。

  2. 繼承的下一個問題是我們將父級的所有方法暴露給客戶端。如果超類別設計得不太正確且包含安全漏洞。那麼,即使我們在子類別的實作中充分考慮了安全性,我們仍然會依賴父類別的有缺陷的實作。

    組合幫助我們提供對超類別方法的受控訪問,而繼承不維護對其方法的任何控制。這也是組合相對於繼承的主要優點之一。

  3. 組合的另一個好處是它增加了呼叫方法時的靈活性。上述類別的實作ClassC不是最佳的,並且使用早期綁定到被呼叫的方法。最小的改變將使我們能夠靈活地呼叫方法並允許後期綁定(在運行時綁定)。

    ClassC.java

    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

    這種方法呼叫的靈活性在繼承中是看不到的,這使得組合成為最好的方法。

  4. 在組合的情況下,單元測試更容易,因為我們知道,對於超類中使用的所有方法,我們可以存根測試,而在繼承中,我們嚴重依賴超類,並且不知道父類的方法如何將會被使用。因此,由於繼承,我們將不得不測試超類別的所有方法,這是不必要的工作。

    理想情況下,只有當父類別和子類別的「 is-a 」關係成立時才應使用繼承,否則應首選組合。

來源文章
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION