为什么Java或C语言中不允许多重继承?

Why is Multiple Inheritance not allowed in Java or C#?

我知道在爪哇和C中不允许多重继承。很多书只是说,多重继承是不允许的。但它可以通过使用接口来实现。没有讨论为什么不允许这样做。有人能告诉我为什么不允许这样做吗?


简短的回答是:因为语言设计者决定不这样做。

基本上,无论是.NET还是Java设计器,都不允许多重继承,因为他们认为增加MI给语言带来了太多的复杂性,同时也带来了太少的好处。

为了更有趣和深入的阅读,有一些文章可以在网络上访问一些语言设计师。例如,对于.NET,Chris Brumme(在CLR的MS工作)已经解释了为什么他们决定不:

  • Different languages actually have different expectations for how MI
    works. For example, how conflicts are
    resolved and whether duplicate bases
    are merged or redundant. Before we can
    implement MI in the CLR, we have to do
    a survey of all the languages, figure
    out the common concepts, and decide
    how to express them in a
    language-neutral manner. We would also
    have to decide whether MI belongs in
    the CLS and what this would mean for
    languages that don't want this concept
    (presumably VB.NET, for example). Of
    course, that's the business we are in
    as a common language runtime, but we
    haven't got around to doing it for MI
    yet.

  • The number of places where MI is truly appropriate is actually quite
    small. In many cases, multiple
    interface inheritance can get the job
    done instead. In other cases, you may
    be able to use encapsulation and
    delegation. If we were to add a
    slightly different construct, like
    mixins, would that actually be more
    powerful?

  • Multiple implementation inheritance injects a lot of complexity into the
    implementation. This complexity
    impacts casting, layout, dispatch,
    field access, serialization, identity
    comparisons, verifiability,
    reflection, generics, and probably
    lots of other places.

  • 你可以在这里阅读全文。

    对于Java,您可以阅读本文:

    The reasons for omitting multiple
    inheritance from the Java language
    mostly stem from the"simple, object
    oriented, and familiar" goal. As a
    simple language, Java's creators
    wanted a language that most developers
    could grasp without extensive
    training. To that end, they worked to
    make the language as similar to C++ as
    possible (familiar) without carrying
    over C++'s unnecessary complexity
    (simple).

    In the designers' opinion, multiple
    inheritance causes more problems and
    confusion than it solves. So they cut
    multiple inheritance from the language
    (just as they cut operator
    overloading). The designers' extensive
    C++ experience taught them that
    multiple inheritance just wasn't worth
    the headache.


    不允许实现的多重继承。

    问题是,如果您有一个牛仔类和一个艺术家类(都带有draw()方法的实现),编译器/运行时无法确定要做什么,然后尝试创建一个新的牛仔艺术家类型。调用draw()方法时会发生什么?是有人躺在街上死了,还是你有一幅可爱的水彩画?

    我相信这就是所谓的双钻石继承问题。


    原因:Java由于其简单性而广受欢迎且易于编码。

    因此,对于程序员来说,Java程序员觉得难以理解和复杂,他们试图避免它。一种这样的属性是多重继承。

  • 他们避开了指针
  • 他们避免了多重继承。
  • 多重继承问题:钻石问题。

    例子:

  • 假设类A有一个方法fun()。B类和C类源自A类。
  • 类B和C都重写方法fun()。
  • 现在假设D类继承了B类和C类(只是假设)。
  • 为类D创建对象。
  • d d=新d-();
  • 并尝试访问d.fun();=>它会调用类B的fun()还是类C的fun()?
  • 这就是钻石问题中存在的模糊性。

    解决这个问题并不是不可能的,但它在阅读时会给程序员带来更多的困惑和复杂性。它引起的问题比它试图解决的问题要多。

    注意:但是任何方法都可以通过使用接口间接实现多个继承。


    因为Java与C++有着截然不同的设计理念。(我不想在这里讨论C。)

    在设计C++时,Stroustrup希望包括有用的特性,而不管它们如何被滥用。通过多重继承、运算符重载、模板和各种其他特性,可能会使时间过多,但也可能对它们做一些非常好的事情。

    Java的设计理念是强调语言结构的安全性。结果是,有些事情做起来更尴尬,但是你可以更加自信,你看到的代码意味着你认为它能做什么。

    此外,Java在很大程度上是来自C++和SimultTalk的反应,它是最著名的面向对象语言。有很多其他的OO语言(公共的Lisp实际上是第一个被标准化的语言),使用不同的OO系统更好地处理MI。

    更不用说,完全可以在Java中使用接口、组合和委派来实现MI。它比C++更明确,因此使用起来笨拙,但会让你在乍一看中更容易理解。

    这里没有正确的答案。有不同的答案,哪一个更适合特定情况取决于应用程序和个人偏好。


    人们避开MI的主要原因(尽管并非唯一原因)是所谓的"菱形问题",这会导致实现中的模糊性。这篇维基百科文章讨论了这一点,并比我能解释得更好。MI还可以导致更复杂的代码,许多OO设计师声称您不需要MI,如果使用它,您的模型可能是错误的。我不确定我是否同意最后一点,但保持简单总是一个好计划。


    在C++中,多重继承是使用不当时的一大难题。为了避免这些流行的设计问题,多个接口"继承"被迫用现代语言(Java,C语言)代替。


    多重继承是

    • 很难理解
    • 很难调试(例如,如果将具有相同命名方法的多个框架中的类深度混合在一起,则会发生非常意外的协同作用)
    • 易误用
    • 不太有用
    • 很难实现,特别是如果你想正确有效地完成它

    因此,不将多继承继承到Java语言中可以被认为是明智的选择。


    另一个原因是单一继承使得强制转换变得简单,不发出汇编程序指令(除了在需要时检查类型的兼容性)。如果您有多个继承,那么您需要确定某个父类在子类中的起始位置。因此,绩效当然是一种额外福利(尽管不是唯一的福利)。


    类的动态加载使多重继承的实现变得困难。

    实际上,在Java中,它们避免了多重继承的复杂性,而不是使用单个继承和接口。在下面解释的情况下,多重继承的复杂性非常高

    多重继承的菱形问题。我们有两个从A继承的类B和C。假设B和C正在重写继承的方法,它们提供自己的实现。现在D从B和C继承,执行多重继承。d应该继承被重写的方法,JVM不能决定将使用哪个被重写的方法?

    在C++中,虚拟函数是用来处理的,我们必须显式地进行。

    这可以通过使用接口来避免,因为没有方法体。接口不能被实例化,它们只能由类实现或由其他接口扩展。


    我接受了"Java中不允许使用多重继承"的说法。

    当一个"类型"从多个"类型"继承时,定义了多重继承。接口也被分类为类型,因为它们有行为。所以Java确实有多重继承。只是它更安全。


    在过去的年代(70年代),当计算机科学更为科学,批量生产更少时,程序员有时间考虑好的设计和良好的实现,因此产品(程序)具有高质量(如TCP/IP设计和实现)。如今,当每个人都在编程,管理者在截止日期前更改规范时,很难跟踪诸如steve haigh post的wikipedia链接中描述的微妙问题;因此,"多重继承"受到编译器设计的限制。如果你喜欢它,你仍然可以使用C++…拥有你想要的所有自由:)


    实际上,如果继承的类具有相同的函数,那么多个继承将产生一个复杂性。也就是说,编译器会有一个混乱的选择(钻石问题)。因此,在Java中,复杂性被移除,并给予接口以获得多继承的功能。我们可以使用接口


    Java具有概念,即多态性。Java中有2种多态性。有方法重载和方法重写。其中,方法重写发生在超类和子类关系中。如果我们正在创建子类的对象并调用超类的方法,并且如果子类扩展了多个类,那么应该调用哪个超类方法?

    或者,当super()调用超类构造函数时,将调用哪个超类构造函数?

    目前Java API的特点是不可能的。因此Java中不允许使用多重继承。


    Java中不允许多重继承,但允许通过接口。

    原因:

    多重继承:引入更多的复杂性和模糊性。

    接口:接口是Java中完全抽象的类,它为您提供了一个统一的方法来适当地从程序的公共可用接口中划出程序的结构或内部操作,其结果是更大的灵活性和可重用代码,以及对如何创建和与O进行交互的更多控制。班级。

    更确切地说,它们是Java中的一种特殊构造,具有额外的特性,允许您执行一种多重继承,即可以向多个类上攻的类。

    让我们举个简单的例子。

  • 假设有两个超类,A类和B类具有相同的方法名,但功能不同。通过以下带有(扩展)关键字的代码,不可能进行多重继承。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
       public class A                              
         {
           void display()
             {
               System.out.println("Hello 'A'");
             }
         }

       public class B                              
          {
            void display()
              {
                System.out.println("Hello 'B'");
              }
          }

      public class C extends A, B    // which is not possible in java
        {
          public static void main(String args[])
            {
              C object = new C();
              object.display();  // Here there is confusion,which display() to call, method from A class or B class
            }
        }
  • 但是,通过接口,可以使用(实现)关键字多重继承。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    interface A
        {
           // display()
        }


     interface B
        {
          //display()
        }

     class C implements A,B
        {
           //main()
           C object = new C();
           (A)object.display();     // call A's display

           (B)object.display(); //call B's display
        }
    }

  • 在C++中,一个类可以从多个类继承(直接或间接),这被称为多重继承。

    但是,C类和Java将类限制为每个类继承的单个继承。从单个父类。

    多重继承是创建结合两个不同类的方面的类的一种有用方法层次结构,当在一个单独的类框架中使用不同的类框架时经常会发生这种情况应用。

    例如,如果两个框架为异常定义了自己的基类,则可以使用多个继承来创建可用于任一框架的异常类。

    多重继承的问题是它可能导致歧义。典型的例子是一个类继承其他两个类,每个类继承同一个类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class A {
        protected:
        bool flag;
    };
    class B : public A {};
    class C : public A {};
    class D : public B, public C {
        public:
        void setFlag( bool nflag ){
            flag = nflag; // ambiguous
        }
    };

    在本例中,flag数据成员由class A定义。但是class Dclass B的后代。和class C,两者都来自A,因此本质上,两份flag的副本可用,因为A的实例位于D的类层次结构中。你想设置哪一个?编译器会抱怨在D中对flag的引用是不明确的。一种解决方法是显式消除引用的歧义:

    1
    B::flag = nflag;

    另一种方法是将b和c声明为virtual base classes,这意味着只有一份a can存在于层次结构中,消除任何歧义。

    其他复杂的情况存在于多重继承中,例如基类的顺序在构造派生对象或无意中隐藏成员的方式时初始化从派生类。为了避免这些复杂性,一些语言将自己局限于更简单的单继承模型。

    虽然这确实大大简化了继承,但也限制了它的实用性。因为只有具有共同祖先的类才能共享行为。接口减轻了这一点通过允许不同层次结构中的类公开公共接口甚至如果它们不是通过共享代码实现的。


    Can anybody tell me precisely why it is not allowed?

    您可以从这个文档链接中找到答案。

    One reason why the Java programming language does not permit you to extend more than one class is to avoid the issues of multiple inheritance of state, which is the ability to inherit fields from multiple classes

    如果允许多重继承,并且通过实例化该类创建对象时,该对象将继承该类所有超级类的字段。这将导致两个问题。

  • 如果来自不同超级类的方法或构造函数实例化了同一个字段怎么办?

  • 哪个方法或构造函数优先?

  • 尽管现在允许多个状态继承,但是您仍然可以实现

    类型的多重继承:类实现多个接口的能力。

    实现的多重继承(通过接口中的默认方法):从多个类继承方法定义的能力

    有关更多信息,请参阅此相关SE问题:

    具有接口的多重继承模糊性


    想象一下这个例子:我有一个班,叫Shape1

    它有CalcualteArea方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Class Shape1
    {

     public void CalculateArea()

         {
           //
         }
    }

    还有一个类Shape2也有相同的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Class Shape2
    {

     public void CalculateArea()

         {

         }
    }

    现在我有了一个儿童类的圆,它既来源于shape1又来源于shape2;

    1
    2
    3
    public class Circle: Shape1, Shape2
    {
    }

    现在,当我为圆创建对象并调用方法时,系统不知道要调用哪个计算面积方法。两者都有相同的签名。所以编译器会混淆。这就是不允许多重继承的原因。

    但是可以有多个接口,因为接口没有方法定义。即使这两个接口都有相同的方法,它们都没有任何实现,子类中的always方法也将被执行。