为什么钻石案例及其共同的祖先被用来解释Java多重继承问题,而不是两个不相关的父类?

这个问题对Java人来说可能听起来很奇怪,但如果你试图解释这一点,那就太好了。

在这些日子里,我正在清除Java的一些非常基本的概念。因此,我来谈谈Java的继承和接口主题。

在阅读本文时,我发现Java不支持多重继承,并且也理解了这一点,我无法理解的是,为什么到处都讨论钻石数字问题(至少4个类来创建钻石)来解释这种行为,难道我们不能只使用3个类来理解这个问题吗?

比如说,我有A类和B类,这两个类是不同的(它们不是普通类的子类),但它们有一个通用的方法,它们看起来像:-

class A {
    void add(int a, int b) {

    }
}

class B {
    void add(int a, int b) {

    }
}

好吧,现在说Java是否支持多重继承,如果有一个类是A和B的子类,就像这样:-

class C extends A,B{ //If this was possible
    @Override
    void add(int a, int b) { 
        // TODO Auto-generated method stub
        super.add(a, b); //Which version of this, from A or B ?
    }
 }

那么编译器将无法找到从A还是B调用的方法,这就是为什么Java不支持多重继承的原因。那么这个概念有什么问题吗?

当我读到这个主题时,我能够理解钻石问题,但我无法理解为什么人们没有用三个类给出例子(如果这是有效的一个,因为我们只使用3个类来演示问题,所以通过将其与钻石问题进行比较很容易理解。

让我知道这个例子是否不适合解释问题,或者这也可以参考来理解问题。

编辑:我在这里得到了一个接近的投票,指出这个问题不清楚。这是主要问题:-

我能理解为什么“Java不支持3个类的多重继承”,如上所述,或者我必须有4个类(菱形结构)才能理解这个问题。


答案 1

钻石继承的问题不在于共同行为,而在于共享状态。如您所见,Java实际上一直支持多重继承,但只支持类型的多重继承

只有三个类,通过引入一个简单的构造(如 或 ),问题相对容易地得到解决。虽然你只看被覆盖的方法,但无论你有一个共同的祖先还是只有基本的三个类,这确实无关紧要。super.Asuper.B

然而,如果有一个共同的祖先,他们都继承了这种状态,那么你就陷入了严重的麻烦。您是否存储了此共同祖先状态的两个单独副本?这更像是组合而不是继承。或者,您是只存储一个由双方共享的 和 ,当他们操作其继承的共享状态时,会导致奇怪的交互?ABAB

class A {
  protected int foo;
}

class B extends A {
  public B() {
    this.foo = 42;
  }
}

class C extends A {
  public C() {
    this.foo = 0xf00;
  }
}

class D extends B,C {
  public D() {
    System.out.println( "Foo is: "+foo ); //Now what?
  }
}

请注意,如果类不存在并且两者都声明了自己的字段,那么上述问题就不会那么大。仍然会有一个名称冲突的问题,但这可以通过一些命名空间结构来解决(也许,就像我们对内部类所做的那样?)。另一方面,真正的钻石问题不仅仅是命名冲突,而是当(和)的两个不相关的超类共享它们都继承的同一状态时,如何保持类不变量的问题。这就是为什么需要所有四个类来演示问题的全部范围。ABCfooB.this.fooC.this.fooDBCA

多重继承中的共享行为不会表现出相同的问题。如此之多,以至于最近引入的默认方法正是这样做的。这意味着现在也允许实现的多重继承。围绕要调用哪个实现的分辨率仍然存在一些复杂性,但是由于接口是无状态的,因此避免了最大的错误。


答案 2

Java不支持多重继承,因为该语言的设计者以这种方式设计了Java。其他语言(如C++支持多重继承就好了,所以这不是一个技术问题,而只是一个设计标准。

多重继承的问题在于,并不总是清楚从哪个类调用哪个方法,以及要访问哪些实例变量。不同的人对它有不同的解释,Java设计人员当时认为最好完全跳过多重继承。

C++通过虚拟继承解决了菱形类问题:

虚拟继承是面向对象编程中使用的一种技术,其中将继承层次结构中的特定基类声明为与进一步派生类中同一基的任何其他包含共享其成员数据实例。例如,如果类 A 通常(非虚拟)派生自类 X(假定包含数据成员),而类 B 也是如此,并且类 C 同时继承自类 A 和 B,则它将包含两组与类 X 关联的数据成员(可独立访问,通常使用适当的消除歧义限定符)。但是,如果类 A 实际上是从类 X 派生的,则类 C 的对象将仅包含类 X 中的一组数据成员。实现此功能的最知名语言是C++。

与Java相反,C++您可以通过在调用前面加上类名来消除要调用的实例方法的歧义:

class X {
  public: virtual void f() { 

  } 
};

class Y : public X {
  public: virtual void f() { 

  } 
};

class Z : public Y {
  public: virtual void f() { 
    X::f();
  } 
};

推荐