Java中的多继承

今天我们将研究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中利用ClassAmethodA()ClassBmethodB(),该怎么办呢?解决方案在于使用组合。这是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 编程的最佳实践之一是“优先选择组合而不是继承”。我们将探讨一些支持这种方法的方面。

  1. 假设我们有一个超类和子类如下: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,它将建议您更改超类或子类中的返回类型。现在想象一下,我们有多级类继承的情况,而超类不受我们控制。我们别无选择,只能更改子类方法签名或其名称以消除编译错误。此外,我们将不得不在所有调用子类方法的地方进行更改,因此继承使我们的代码变得脆弱。这种问题在组合中永远不会发生,这使得它比继承更受青睐。

  2. 继承的另一个问题是,我们将超类的所有方法暴露给客户端,如果我们的超类设计不当且存在安全漏洞,即使我们在实现类时完全小心,我们也会受到超类设计不良的影响。组合帮助我们提供对超类方法的受控访问,而继承不提供对超类方法的任何控制,这也是组合优于继承的主要优势之一。

  3. 组合的另一个好处是它提供了对方法调用的灵活性。我们上面对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实现
    

    这种方法调用的灵活性在继承中是不可用的,并且促使最佳实践支持组合优于继承。

  4. 在组合中进行单元测试很容易,因为我们知道我们从超类中使用了哪些方法,我们可以为测试进行模拟,而在继承中,我们严重依赖于超类,并不知道超类的所有方法将被使用,因此我们需要测试超类的所有方法,这是额外的工作,而且由于继承,我们不得不做这样的工作。

这就是关于Java中多重继承的全部内容,以及对组合的简要介绍。

Source:
https://www.digitalocean.com/community/tutorials/multiple-inheritance-in-java