JavaRush /Java 博客 /Random-ZH /Java 中的多重继承。组合与继承
DSergey_Kh
第 12 级

Java 中的多重继承。组合与继承

已在 Random-ZH 群组中发布

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