Java中的多重继承
今天,我们将研究Java中的多重继承。
有时我写了几篇有关Java继承,接口和组成的文章。
在本文中,我们将研究Java多重继承,然后比较组成和继承。
Java中的多重继承
Java中的多重继承是创建具有多个超类的单个类的能力。
与其他一些流行的面向对象的编程语言(如C++)不同,java不提供对类中多重继承的支持。
Java不支持类中的多重继承,因为它可能导致菱形问题,而不是提供解决复杂问题的方法,而是有更好的方法来实现与多重继承相同的结果。
Java中的钻石问题
为了轻松理解钻石问题,我们假设Java支持多种继承。
在这种情况下,我们可以像下面的图像那样有一个类层次结构。
假设SuperClass是一个抽象类,声明了一些方法,而ClassA,ClassB是具体类。
SuperClass.java
package com.theitroad.comheritance;
public abstract class SuperClass {
public abstract void doSomething();
}
ClassA.java
package com.theitroad.comheritance;
public class ClassA extends SuperClass{
@Override
public void doSomething(){
System.out.println("doSomething implementation of A");
}
//ClassA own method
public void methodA(){
}
}
ClassB.java
package com.theitroad.comheritance;
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。
ClassC.java
package com.theitroad.comheritance;
//this is just an assumption to explain the diamond problem
//this code won't compile
public class ClassC extends ClassA, ClassB{
public void test(){
//calling super class method
doSomething();
}
}
注意,test()方法正在调用超类doSomething()方法。
由于编译器不知道要执行哪个超类方法,所以这导致歧义。
由于是菱形的类图,因此在Java中称为"菱形问题"。
Java中的菱形问题是Java不支持类中的多个继承的主要原因。
请注意,具有多个类继承的上述问题也可能只出现在三个类中,它们全部具有至少一个通用方法。
Java接口中的多重继承
您可能已经注意到,我一直在说多重继承在类中是不支持的,但在接口中是受支持的。
单个接口可以扩展多个接口,下面是一个简单示例。
InterfaceA.java
package com.theitroad.comheritance;
public interface InterfaceA {
public void doSomething();
}
InterfaceB.java
package com.theitroad.comheritance;
public interface InterfaceB {
public void doSomething();
}
注意,这两个接口都声明了相同的方法,现在我们可以拥有一个扩展这两个接口的接口,如下所示。
InterfaceC.java
package com.theitroad.comheritance;
public interface InterfaceC extends InterfaceA, InterfaceB {
//same method is declared in InterfaceA and InterfaceB both
public void doSomething();
}
这很好,因为接口仅声明方法,并且实际实现将由实现接口的具体类完成。
因此,Java接口中的多重继承不存在任何歧义。
这就是Java类可以实现多个接口的原因,如下例所示。
InterfacesImpl.java
package com.theitroad.comheritance;
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注解。
覆盖注释是三个内置的Java注释之一,并且在覆盖任何方法时都应始终使用覆盖注释。
rescue组合
因此,如果我们想在ClassC中使用ClassA函数methodA()和ClassB函数methodB()怎么办。
解决方案在于使用合成。
这是ClassC的重构版本,它使用合成来利用两个类方法以及一个对象中的doSomething()方法。
ClassC.java
package com.theitroad.comheritance;
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编程的最佳实践之一是"偏向于继承而不是继承"。
我们将研究有利于这种方法的某些方面。
假设我们有一个超类和子类,如下所示:ClassC.java
ClassD.java
上面的代码可以编译并正常工作,但是如果ClassC实现更改如下:
注意子类中已经存在test()方法,但是返回类型不同。
现在,ClassD将无法编译,并且如果您使用的是任何IDE,它会建议您更改超类或者子类中的返回类型。
现在,想象一下我们具有多个级别的类继承并且超类不受我们控制的情况。
我们别无选择,只能更改子类方法签名或者其名称以消除编译错误。
同样,我们将不得不在所有调用子类方法的地方进行更改,因此继承使我们的代码易碎。
使用组合永远不会发生上述问题,这使得它比继承更有利。继承的另一个问题是,我们将所有超类方法公开给客户端,并且如果我们的超类设计不当且存在安全漏洞,那么即使我们在实现类时全神贯注,我们也会受到不良实现的影响superclass.Composition帮助我们提供对超类方法的受控访问,而继承不提供对超类方法的任何控制,这也是与继承相比,composition的主要优点之一。
组合的另一个好处是它提供了方法调用的灵活性。
我们上面的ClassC实现不是最佳的,它提供了将要调用的方法的编译时绑定,只需进行最小的更改,我们就可以灵活地使方法调用并使之动态。
上面程序的输出是:
这种方法调用的灵活性在继承中不可用,从而提倡了最佳做法,即在继承方面偏向于组合。
- 单元测试很容易组合,因为我们知道超类中正在使用的所有方法,并且可以对它们进行模拟,以进行测试,而在继承中,我们很大程度上依赖于超类,并且不知道将使用所有超类方法,因此我们需要要测试超类的所有方法,这是一项另外的工作,由于继承,我们不需要这样做。

