今天我们将研究Java中的多重继承。之前我写了一些关于继承、接口和组合在Java中的帖子。在这篇帖子中,我们将探讨Java中的多重继承,然后比较组合和继承。
Java中的多重继承
Java中的多重继承是创建具有多个超类的单个类的能力。与一些其他流行的面向对象编程语言(如C++)不同,Java不支持类的多重继承。Java不支持类的多重继承,因为它可能导致菱形问题,而不是提供一些复杂的解决方法,有更好的方式可以实现与多重继承相同的结果。
Java中的菱形问题
要轻松理解菱形问题,让我们假设Java支持多重继承。在这种情况下,我们可以有一个类层次结构,如下图所示。 假设SuperClass是一个抽象类,声明了一些方法,而ClassA、ClassB是具体类。
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("doSomething implementation of A");
}
//ClassA拥有的方法
public void methodA(){
}
}
ClassB.java
package com.journaldev.inheritance;
public class ClassB extends SuperClass{
@Override
public void doSomething(){
System.out.println("doSomething implementation of B");
}
//ClassB特有的方法
public void methodB(){
}
}
现在假设ClassC的实现如下,并且它同时扩展了ClassA和ClassB。 ClassC.java
package com.journaldev.inheritance;
// 这只是为了解释菱形问题而假设的
//这段代码不会编译
public class ClassC extends ClassA, ClassB{
public void test(){
//调用超类方法
doSomething();
}
}
请注意,test()
方法调用了超类 doSomething()
方法。这导致了歧义,因为编译器无法确定要执行哪个超类方法。由于菱形类图的存在,这在Java中被称为菱形问题。Java中的菱形问题是Java不支持类的多继承的主要原因。请注意,具有多个类继承的上述问题也可能仅涉及三个类,其中所有类都至少有一个共同的方法。
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接口中不存在任何多重继承的歧义可能性。这就是为什么一个Java类可以实现多个接口的原因,就像下面的例子一样。 InterfacesImpl.java
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();
//所有以下的方法调用都将指向相同的具体实现
objA.doSomething();
objB.doSomething();
objC.doSomething();
}
}
你是否注意到,每当我重写任何超类方法或实现任何接口方法时,我都使用@Override注解。Override注解是三个内置Java注解之一,我们应该在重写任何方法时始终使用override注解。
组合拯救
那么,如果我们想要在ClassC
中利用ClassA
的methodA()
和ClassB
的methodB()
,该怎么办呢?解决方案在于使用组合。这是ClassC的一个重构版本,使用组合来利用两个类的方法,并且还使用了其中一个对象的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();
}
}
组合 vs 继承
Java 编程的最佳实践之一是“优先选择组合而不是继承”。我们将探讨一些支持这种方法的方面。
-
假设我们有一个超类和子类如下:
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的实现更改如下会怎样:
ClassC.java
package com.journaldev.inheritance; public class ClassC{ public void methodC(){ } public void test(){ } }
请注意,
test()
方法已经存在于子类中,但返回类型不同。现在ClassD将无法编译,如果您使用任何IDE,它将建议您更改超类或子类中的返回类型。现在想象一下,我们有多级类继承的情况,而超类不受我们控制。我们别无选择,只能更改子类方法签名或其名称以消除编译错误。此外,我们将不得不在所有调用子类方法的地方进行更改,因此继承使我们的代码变得脆弱。这种问题在组合中永远不会发生,这使得它比继承更受青睐。 -
继承的另一个问题是,我们将超类的所有方法暴露给客户端,如果我们的超类设计不当且存在安全漏洞,即使我们在实现类时完全小心,我们也会受到超类设计不良的影响。组合帮助我们提供对超类方法的受控访问,而继承不提供对超类方法的任何控制,这也是组合优于继承的主要优势之一。
-
组合的另一个好处是它提供了对方法调用的灵活性。我们上面对
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(); } }
以上程序的输出是:
A的doSomething实现 B的doSomething实现
这种方法调用的灵活性在继承中是不可用的,并且促使最佳实践支持组合优于继承。
-
在组合中进行单元测试很容易,因为我们知道我们从超类中使用了哪些方法,我们可以为测试进行模拟,而在继承中,我们严重依赖于超类,并不知道超类的所有方法将被使用,因此我们需要测试超类的所有方法,这是额外的工作,而且由于继承,我们不得不做这样的工作。
这就是关于Java中多重继承的全部内容,以及对组合的简要介绍。
Source:
https://www.digitalocean.com/community/tutorials/multiple-inheritance-in-java