JavaRush /Java Blog /Random-TW /Java 中的多重繼承。組合與繼承
DSergey_Kh
等級 12

Java 中的多重繼承。組合與繼承

在 Random-TW 群組發布

Java中的多重繼承

多重繼承可讓您建立從多個超類別繼承的類別。與其他一些流行的物件導向程式語言(例如 C++)不同,Java 不允許從類別進行多重繼承。Java 不支援多類別繼承,因為它可能導致鑽石問題。我們不需要尋找解決這個問題的方法,而是有更好的選擇來實現相同的結果,例如多重繼承。

鑽石問題

為了更容易理解鑽石問題,我們假設 Java 支持多重繼承。在這種情況下,我們可以有一個類別層次結構,如下圖所示。 Java 中的多重繼承。 組合與繼承 - 1我們假設該類別SuperClass是抽象的,並且在其中聲明了一些方法。具體類ClassAClassB.
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並繼承它ClassAClassB
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()ClassBClassCClassCdoSomething()
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. 假設我們有一個超類別和一個擴展它的類別:

    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,它會提示您變更超類別或子類別中的返回類型。

    現在想像一下這樣一種情況,我們有一個多層級類別繼承層次結構,並且沒有權限存取超類別。我們別無選擇,只能更改子類別方法簽章或其名稱來消除編譯錯誤。我們還必須在所有呼叫子類別方法的地方更改它。因此,繼承使我們的程式碼變得脆弱。

    上述問題在組合中永遠不會發生,這使得繼承更具吸引力。

  2. 繼承的另一個問題是,我們將超類別的所有方法暴露給客戶端,如果我們的超類別設計不正確並且存在安全漏洞,那麼即使我們實現了類別的最佳實現,我們也會受到不良實現的影響超類別的。組合幫助我們提供對超類別方法的受控訪問,而繼承不提供對超類別方法的控制。這也是繼承組合的主要好處之一。

  3. 組合的另一個優點是它允許靈活地呼叫方法。我們對上面提供的類別的實作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

    繼承無法提供方法呼叫的靈活性,這為組合選擇帶來了另一個好處。

  4. 使用組合更容易進行單元測試,因為我們知道我們正在使用超類別中的所有方法,並且可以複製它們進行測試。而在繼承中,我們更依賴超類,並且不知道將使用的超類別的所有方法。所以我們必須測試超類別的所有方法,由於繼承,這是額外的工作。

    理想情況下,只有當子類別與超類別的關係定義為「is」時,我們才應該使用繼承。在所有其他情況下,建議使用該組合物。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION