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