JavaRush /Java 博客 /Random-ZH /Java 中的多重继承。组成和继承的比较
HonyaSaar
第 26 级
Москва

Java 中的多重继承。组成和继承的比较

已在 Random-ZH 群组中发布
前段时间我写了几篇关于Java中的继承、接口和组合的文章。在本文中,我们将了解多重继承,然后了解组合相对于继承的优势。
Java 中的多重继承。 组成和继承的比较 - 1

Java中的多重继承

多重继承是创建具有多个父类的类的能力。与C++等其他流行的面向对象语言不同,Java不支持多类继承。他不支持它,因为可能会遇到“钻石问题”,而是更愿意提供某种综合方法来解决它,使用我们可以实现类似继承结果的最佳选项。

《钻石问题》

为了更简单地理解钻石问题,我们假设Java支持多重继承。在这种情况下,我们可以获得具有如下图所示层次结构的类。 钻石级层次结构我们假设 SuperClass是一个描述某种方法的抽象类,并且类 ClassAClassB是真实的类。 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("Какая-то реализация класса A");
    }
  //собственный метод класса  ClassA
    public void methodA(){
    }
}
现在,我们假设该类同时 ClassC继承自 ClassA和,并且同时具有以下实现: ClassB
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
    public void test(){
        //вызов метода родительского класса
        doSomething();
    }
}
注意,该方法调用了父类的 test()方法,这会导致歧义,因为编译器不知道应该调用哪个超类方法。 doSomething()由于这种情况下类继承图的形状类似于多面钻石的轮廓,因此该问题被称为“钻石问题”。这就是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中的类可以继承多个接口。让我们用下面的例子来展示它。 InterfacesImpl.java
package com.journaldev.inheritance;

public class InterfacesImpl implements InterfaceA, InterfaceB, InterfaceC {

    @Override
    public void doSomething() {
        System.out.println("doSomething реализация реального класса ");
    }

    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 注释。这是三个内置 Java 注释之一,在重写方法时应始终使用它。

作曲作为救赎

那么如果我们想在 中使用 methodA()ClassAmethodB()类函数怎么办?解决这个问题的方法可能是组合——一种重写版本,它实现了两个类方法,并且还具有其中一个对象的 实现。 ClassB ClassС ClassC ClassA ClassB 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();
    }
}

组合还是继承?

利用组合而不是继承是一种很好的 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以不同的方式实现会怎样:

    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();
        }
    }

    上面的程序会显示:

    doSomething implementation of A
    doSomething implementation of B

    这种方法调用的灵活性在继承中是看不到的,这使得组合成为最好的方法。

  4. 在组合的情况下,单元测试更容易,因为我们知道,对于超类中使用的所有方法,我们可以存根测试,而在继承中,我们严重依赖超类,并且不知道父类的方法如何将会被使用。因此,由于继承,我们将不得不测试超类的所有方法,这是不必要的工作。

    理想情况下,只有当父类和子类的“ is-a ”关系成立时才应使用继承,否则应首选组合。

来源文章
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION