简体   繁体   English

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

[英]Why is the diamond case with its common ancestor used to explain Java multiple inheritance issue, instead of two unrelated parent classes?

This question might sound weird to Java people but if you try to explain this, it would be great. 对于Java人来说,这个问题可能听起来很奇怪,但如果你试图解释这个问题,那就太好了。

In these days I am clearing some of Java's very basic concept. 在这些日子里,我正在清除Java的一些非常基本的概念。 So I come to Inheritance and Interface topic of Java. 所以我来谈谈Java的继承和接口主题。

While reading this I found that Java does not support Multiple Inheritance and also understood that, what I am not able to understand that why everywhere Diamond figure issue(At least 4 class to create diamond) is discussed to explain this behavior, Can't we understand this issue using 3 classes only. 在阅读本文时,我发现Java不支持多重继承,并且也明白,我无法理解为什么到处都会出现Diamond图形问题(至少有4个类来创建钻石)来解释这种行为,我们不能仅使用3个类来理解此问题。

Say, I have class A and class B, these two classes are different (they are not child class of common class) but they have one common method and they look like :- 说,我有A类和B类,这两个类是不同的(它们不是普通类的子类)但是它们有一个共同的方法,它们看起来像:

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

    }
}

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

    }
}

Ok,Now say if Java supports Multiple inheritance and if there is one class which is the subclass of A and B like this :- 好的,现在说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 ?
    }
 }

then compiler will not be able to find which method to call whether from A or B and that is why Java does not support Multiple Inheritance. 然后编译器将无法找到从A或B调用哪个方法,这就是Java不支持多重继承的原因。 So is there any thing wrong with this concept ? 那么这个概念有什么问题吗?

When I read about this topic I was able to understand Diamond issue, but I am not able to understand why people are not giving example with three class (If this is valid one, because we used only 3 classes to demonstrate issue so its easy to understand by comparing it to Diamond issue.) 当我读到这个主题时,我能够理解钻石问题,但是我无法理解为什么人们没有给出三个类的例子(如果这是有效的一个,因为我们只使用了3个类来演示问题所以它很容易通过将其与钻石问题进行比较来理解。)

Let me know whether this example does not fit to explain issue or this can also be referred to understand issue. 让我知道这个例子是否不适合解释问题,或者这也可以参考理解问题。

Edit: I got one close vote here stating that question is not clear. 编辑:我在这里得到一个近距离投票,说明问题不明确。 Here is main question :- 这是一个主要问题: -

Can I understand Why "Java does not support Multiple Inheritance" with 3 classes only as described above or I must need to have 4 classes (Diamond structure) to understand the issue. 我能理解为什么“Java不支持多重继承”只有3个类,如上所述,或者我必须要有4个类(Diamond结构)来理解这个问题。

The problem with diamond inheritance is not so much shared behaviour but shared state . 钻石继承的问题不是共享行为,而是共享状态 As you can see, Java in fact has always supported multiple inheritance, but only multiple inheritance of type . 如您所见,Java实际上始终支持多重继承,但只支持多种类型的继承

With only three classes the problem is resolved relatively easily by introducing a simple construct like super.A or super.B . 只有三个类,通过引入像super.Asuper.B这样的简单构造,可以相对容易地解决问题。 And while you're only looking at overridden methods, it indeed doesn't matter whether you have a common ancestor or just the basic three classes. 虽然你只关注被覆盖的方法,但无论你是拥有共同的祖先还是只有基本的三个类,这都无关紧要。

However if A and B have a common ancestor, the state of which they both inherit, then you're in serious trouble. 但是,如果AB有一个共同的祖先,他们都继承的状态,那么你就会遇到严重的麻烦。 Do you store two separate copies of the state of this common ancestor? 你是否存储了这个共同祖先的两个独立副本? That would be more like composition than inheritance. 这更像是构图而不是继承。 Or do you only store one that is shared by both A and B , causing strange interactions when they manipulate their inherited shared state? 或者你只存储AB共享的A ,当他们操纵他们继承的共享状态时会导致奇怪的交互?

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?
  }
}

Note how the above wouldn't be so big a problem if class A didn't exist and both B and C declared their own foo field. 注意如果A类不存在并且BC声明了它们自己的foo字段,上面的问题将不会是如此大的问题。 There would still be a problem of clashing names, but that could be resolved with some namespacing construct ( B.this.foo and C.this.foo maybe, as we do with inner classes?). 仍然存在冲突名称的问题,但这可以通过一些命名空间构造来解决( B.this.fooC.this.foo也许,就像我们对内部类做的那样?)。 The true diamond problem on the other hand is more than a naming clash, it's a matter of how to maintain class invariants when two unrelated superclasses of D ( B and C ) share the same state they both inherit from A . 另一方面,真正的钻石问题不仅仅是命名冲突,而是当两个不相关的DBC )超级类共享它们都从A继承的相同状态时,如何维护类不变量。 This is why all four classes are needed to demonstrate the full extent of the problem. 这就是为什么需要所有四个类来展示问题的全部范围的原因。

Shared behaviour in multiple inheritance doesn't exhibit the same problem. 多继承中的共享行为不会出现同样的问题。 So much so that the recently introduced default methods do exactly that. 以至于最近推出的默认方法就是这样做的。 This means that multiple inheritance of implementations is allowed now too. 这意味着现在也允许实现多重继承。 There is still some complication around the resolution of which implementation to call but since interfaces are stateless, the biggest bugbear is avoided. 关于调用哪个实现的解决方案仍然存在一些复杂因素,但由于接口是无状态的,因此避免了最大的错误。

Java does not support multiple inheritance because the designers of the language designed Java this way. Java不支持多重继承,因为该语言的设计者以这种方式设计Java。 Other languages like C++ support multiple inheritance just fine, so it is not a technical issue but just a design criteria. 其他语言(如C ++)支持多重继承,因此它不是技术问题,只是设计标准。

The problem with multiple inheritance is that it is not always clear which method from which class you are calling to, and which instance variables you are accessing to. 多重继承的问题在于,并不总是清楚从哪个类调用哪个方法,以及要访问的实例变量。 Different people interpret it differently and the Java designers believed at that time that it was better to skip multiple inheritance altogether. 不同的人对它的解释不同,Java设计者当时认为最好完全跳过多重继承。

C++ solves the diamond shaped class issue with virtual inheritance : C ++通过虚拟继承解决了菱形类问题:

Virtual inheritance is a technique used in object-oriented programming, where a particular base class in an inheritance hierarchy is declared to share its member data instances with any other inclusions of that same base in further derived classes. 虚拟继承是面向对象编程中使用的一种技术,其中声明继承层次结构中的特定基类与其他派生类中的同一基础的任何其他包含共享其成员数据实例。 For example, if class A is normally (non-virtually) derived from class X (assumed to contain data members), and class B likewise, and class C inherits from both classes A and B, it will contain two sets of the data members associated with class X (accessible independently, often with suitable disambiguating qualifiers). 例如,如果类A通常(非虚拟地)派生自类X(假设包含数据成员),同样类B,并且类C继承自类A和B,则它将包含两组数据成员与X类相关联(可以独立访问,通常使用合适的消除歧义的限定符)。 But if class A is virtually derived from class X instead, then objects of class C will contain only one set of the data members from class X. The best-known language that implements this feature is C++. 但是,如果类A实际上是从类X派生而来,那么类C的对象将只包含来自类X的一组数据成员。实现此功能的最着名的语言是C ++。

Contrary to Java, in C++ you can disambiguate which instance method to call by prefixing the call with the name of the class: 与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();
  } 
};

That is just one difficulty that you have to solve for multiple inheritance in a language. 这只是您在语言中进行多重继承时必须解决的一个难题。 Since there exist languages that do have multiple inhertance (eg Common Lisp, C++, Eiffel), it is obviously not insurmountable. 由于存在具有多个内容的语言(例如Common Lisp,C ++,Eiffel),它显然不是不可克服的。

Common Lisp defines the exact strategy for priorizing (ordering) the inheritance graph, so there is no ambiguity in the rare cases where it does matter in practice. Common Lisp定义了对继承图进行优先级排序的确切策略,因此在实际中它很重要的极少数情况下没有歧义。

C++ uses virtual inheritance (I have not yet put the effort in to understand what that means). C ++使用虚拟继承(我还没有努力去理解这意味着什么)。

Eiffel allows to specify exactly how you want to inherit, possibly renaming methods in the subclass. Eiffel允许指定您想要继承的方式,可能在子类中重命名方法。

Java just skipped multiple inheritance. Java刚跳过多重继承。 (I personally think that it is difficult not to be insulting to its designers while trying to rationalize this decision. Of course, it is hard to think about a thing when the language you think in does not support it.) (我个人认为,这是很难不被侮辱它的设计者试图理顺这个决定。当然,这是很难想到的事情时,语言,你觉得不支持它。)

The diamond problem with four classes is simpler than three classes problem described in the question. 四个类的钻石问题比问题中描述的三个类问题简单。

The problem with three classes adds another issue that has to be solved first: The naming conflict caused by two unrelated add methods with the same signature. 三个类的问题增加了另一个必须首先解决的问题:由具有相同签名的两个不相关的add方法引起的命名冲突。 It's not actually hard to solve but it adds unnecessary complexity. 它实际上并不难解决,但它增加了不必要的复杂性。 It would probably be allowed in Java (like it is already allowed to implement multiple unrelated interface methods with the same signature), but there could be languages that simply prohibit multiple inheritance of similar methods without a common ancestor. 它可能在Java中被允许(就像它已经允许使用相同的签名实现多个不相关的接口方法),但是可能存在一些语言,它们只是禁止在没有共同祖先的情况下对类似方法进行多重继承。

By adding a fourth class which defines add , it is clear that both A and B are implementing the same add method. 通过添加定义add的第四个类,很明显AB都实现了相同的 add方法。

So the diamond problem is more clear because it shows the problem using simple inheritance instead of just using methods with the same signature. 因此钻石问题更加清晰,因为它使用简单继承而不是仅使用具有相同签名的方法来显示问题。 It is well known and probably applies to a wider range of programming languages, so the variation of it with three classes (which adds the additional naming conflict issue) doesn't make much sense and therefore hasn't been adopted. 它是众所周知的,可能适用于更广泛的编程语言,因此它与三个类的变化(这增加了额外的命名冲突问题)没有多大意义,因此没有被采用。

Multiple inheritance is no problem if you find a sane solution to method resolution. 如果您找到一个理智的方法解决方案,多重继承是没有问题的。 When you invoke a method on an object, method resolution is the act of picking which class'es version of the method should be used. 当您在对象上调用方法时,方法解析是选择应该使用哪个类的方法版本的行为。 For this, the inheritance graph is linearized. 为此,继承图是线性化的。 Then, the first class in this sequence that provides an implementation of the requested method is chosen. 然后,选择提供所请求方法的实现的该序列中的第一个类。

To illustrate the following examples of conflict resolution strategies, we will be using this diamond inheritance graph: 为了说明冲突解决策略的以下示例,我们将使用此钻石继承图:

    +-----+
    |  A  |
    |=====|
    |foo()|
    +-----+
       ^
       |
   +---+---+
   |       |
+-----+ +-----+
|  B  | |  C  |
|=====| |=====|
|foo()| |foo()|
+-----+ +-----+
   ^       ^
   |       |
   +---+---+
       |
    +-----+
    |  D  |
    |=====|
    +-----+
  • The most flexible strategy is requiring the programmer to explicitly choose the implementation when creating an ambiguous class, by explicitly overriding the conflicting method. 最灵活的策略是要求程序员在创建不明确的类时明确地选择实现,方法是明确地覆盖冲突的方法。 A variant of this is banning multiple inheritance. 这种变体禁止多重继承。 If a programmer wants to inherit behaviour from multiple classes, composition will have to be used, and a number of proxy methods to be written. 如果程序员想要从多个类继承行为,则必须使用组合,并编写许多代理方法。 However, naively explicitly resolved inheritance conflicts has the same shortcomings as… 然而,天真明确解决的继承冲突具有与......相同的缺点。

  • Depth first search , which might create the linearization D, B, A, C . 深度优先搜索 ,可能会创建线性化D, B, A, C But this way, A::foo() is considered before C::foo() although C::foo() overrides A::foo() ! 不过这样一来, A::foo()被认为是前C::foo()虽然C::foo() 覆盖 A::foo() This can't be what we wanted. 这不是我们想要的。 An example of a language using DFS is Perl. 使用DFS的语言示例是Perl。

  • Use a clever algorithm that guarantees that if X is a subclass of Y , it will always come before Y in the linearization. 使用一个聪明的算法 ,保证如果XY的子类,它将始终位于线性化中的Y之前。 Such an algorithm will not be able to unravel all inheritance graphs, but it provides sane semantics in most cases: If a class overrides a method, it will always be preferred over the overridden method. 这样的算法将无法解开所有继承图,但它在大多数情况下提供了理智的语义:如果一个类重写了一个方法,它将始终优先于重写方法。 This algorithm exists and is called C3 . 该算法存在并称为C3 This would create the linearization D, B, C, A . 这将产生线性化D, B, C, A C3 was first presented in 1996. Unfortunately, Java was published in 1995 – so C3 was not known when Java was initially designed. C3于1996年首次出现。不幸的是,Java于1995年发布 - 因此在最初设计Java时不知道C3。

  • Use composition, not inheritance – revisited. 使用组合,而不是继承 - 重访。 Some solutions to multiple inheritance suggest getting rid of the “class inheritance” bit, and instead propose other units of composition. 多继承的一些解决方案建议摆脱“类继承”位,而是提出其他组合单元。 One example is mixins , which “copy & paste” method definitions into your class. 一个例子是mixins ,它将方法定义“复制并粘贴”到您的类中。 This is incredibly crude. 这非常粗糙。

    The idea of mixins has been refined into traits (presented 2002, also too late for Java). mixins的想法已被细化为特征 (2002年呈现,对Java而言也为时已晚)。 Traits are a more general case of both classes and interfaces. 特征是类和接口的更一般情况。 When you “inherit” a trait, the definitions are embedded into your class, so that this does not complicate method resolution. 当您“继承”特征时,定义将嵌入到您的类中,这样就不会使方法解析复杂化。 Unlike mixins, traits provide more nuanced strategies to resolve conflicts. 与mixins不同,traits提供了更加细微的策略来解决冲突。 Especially, the order in which traits are composed matters. 特别是,特征组成的顺序很重要。 Traits play a prominent role in Perl's “Moose” object system (called roles ) and in Scala . Traits在Perl的“Moose”对象系统(称为角色 )和Scala中扮演着重要角色。

Java prefers single inheritance for another reason: If each class can only have one superclass, we don't have to apply complicated method resolution algorithms – the inheritance chain already is the method resolution order. Java更喜欢单继承:另一个原因:如果每个类只能有一个超类,我们就不必应用复杂的方法解析算法 - 继承链已经是方法解析顺序。 This makes methods a bit more efficient. 这使方法更有效。

Java 8 introduced default methods which look similar to traits. Java 8引入了与traits类似的默认方法。 However, the rules of Java's method resolution make interfaces with default methods much less capable than traits. 但是,Java方法解析的规则使得使用默认方法的接口的能力远远低于特征。 Still, a step in the direction of adapting modern solutions to the multiple-inheritance problem. 仍然是朝着使现代解决方案适应多重继承问题的方向迈出的一步。

In most multiple inheritance method resolution schemes, the order of superclasses does matter. 在大多数多继承方法解析方案中,超类的顺序很重要。 That is, there's a difference between class D extends B, C and class D extends C, B . 也就是说, class D extends B, Cclass D extends C, B之间存在差异。 Because the order can be used for simple disambiguation, a three-class example does not sufficiently demonstrate the problems associated with multiple inheritance. 由于订单可用于简单的消歧,因此三类示例无法充分展示与多重继承相关的问题。 You need a full four-class diamond problem for that, as it shows how naive depth-first search leads to an unintuitive method resolution order. 你需要一个完整的四级钻石问题,因为它显示了天真的深度优先搜索导致一个不直观的方法解析顺序。

The diamond problem that you cite is one reason (and a very good one) not to support multiple inheritance. 你引用的钻石问题是不支持多重继承的一个原因(也是一个非常好的原因)。 There are other reasons too, which you can read a bit about here . 还有其他原因,你可以在这里阅读一下

A lot of the reasons boil down to complexity (it's quite complex to do it right), the relatively rare legitimate need to use it, and all sorts of other problems with dispatching (aside from just the diamond problem) that it creates. 很多原因归结为复杂性(做正确的事情非常复杂),使用它的相对罕见的合法需求,以及它所创造的调度(除了钻石问题之外)的各种其他问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM