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

时间:2009-06-15 09:49:14

标签: c# java language-design multiple-inheritance

我知道Java和C#中不允许多重继承。很多书只是说,不允许多重继承。但它可以通过使用接口来实现。没有讨论为什么不允许它。任何人都可以告诉我为什么不允许它?

17 个答案:

答案 0 :(得分:134)

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

基本上,似乎.NET和Java设计人员都不允许多重继承,因为他们认为增加MI 为语言增加了太多的复杂性,同时提供太少的好处

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

  
      
  1. 不同的语言实际上对MI的方式有不同的期望   作品。例如,冲突是怎样的   解决了是否重复的基础   合并或冗余。在我们能做之前   在CLR中实现MI,我们必须这样做   图中所有语言的调查   出于共同的概念,并做出决定   如何表达它们   语言中立的方式。我们也会   必须决定MI是否属于   CLS以及这意味着什么   不想要这个概念的语言   (大概是VB.NET,例如)。的   当然,这就是我们所处的业务   作为公共语言运行库,但我们   没有为MI做这件事   爱好。

  2.   
  3. MI真正合适的地方实际上是相当的   小。在许多情况下,多个   接口继承可以获得工作   完成了。在其他情况下,你可以   能够使用封装和   代表团。如果我们要添加一个   略有不同的构造,如   mixins,实际上会更多   强大?

  4.   
  5. 多个实现继承为其注入了大量复杂性   实现。这种复杂性   影响铸造,布局,调度,   字段访问,序列化,身份   比较,可验证性,   反思,泛型,可能   很多其他地方。

  6.   

You can read the full article here.

对于Java,您可以阅读this article

  

省略多重的原因   从Java语言继承   主要源于“简单,对象   面向,熟悉的“目标。作为一个   简单的语言,Java的创造者   想要一种大多数开发者的语言   没有广泛的把握   训练。为此,他们努力工作   使语言与C ++类似   可能(熟悉的)没有携带   超过C ++的不必要的复杂性   (简单)。

     

在设计师看来,多重   继承导致更多的问题和   比它解决的混乱。所以他们削减了   语言的多重继承   (就像他们削减操作员一样   超载)。设计师的广泛   C ++经验告诉他们   多重继承只是不值得   头痛。

答案 1 :(得分:91)

实现的多重继承是不允许的。

问题是,如果你有一个Cowboy和Artist类,编译器/运行时无法弄清楚该怎么做,都有draw()方法的实现,然后你尝试创建一个新的CowboyArtist类型。调用draw()方法时会发生什么?有人躺在街上死了,或者你有一个可爱的水彩画?

我相信它被称为双钻石继承问题。

答案 2 :(得分:19)

<强>原因: Java非常流行且易于编码,因为它简单。

因此,对于程序员来说,Java开发人员感到困难和复杂,他们试图避免它。一种这样的属性是多重继承。

  1. 他们避免使用指针
  2. 他们避免了多重继承。
  3. 多重继承问题:钻石问题。

    示例

    1. 假设A类有一个方法fun()。 B类和C类来自A类。
    2. B和C两个类都覆盖了方法fun()。
    3. 现在假设D类继承了B类和C类(只是假设)
    4. 为D类创建对象。
    5. D d = new D();
    6. 并尝试访问d.fun(); =&GT;它会调用B类的乐趣()或C类的乐趣()吗?
    7. 这是钻石问题中存在的歧义。

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

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

答案 3 :(得分:13)

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

在设计C ++时,Stroustrup希望包含有用的功能,无论它们如何被滥用。可以通过多重继承,运算符重载,模板和各种其他功能搞砸大时间,但也可以用它们做一些非常好的事情。

Java设计理念是强调语言结构的安全性。结果是有些事情要做得更尴尬,但你可以更自信地看到你所看到的代码意味着它的作用。

此外,Java在很大程度上是来自C ++和Smalltalk(最着名的OO语言)的反应。还有很多其他的OO语言(Common Lisp实际上是第一个被标准化的语言),有不同的OO系统可以更好地处理MI。

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

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

答案 4 :(得分:12)

人们避开MI的主要原因(尽管绝不是唯一的)是所谓的“钻石问题”,导致你的实施模糊不清。这个wikipedia article讨论了它并且比我更好地解释了。 MI也可以导致更复杂的代码,许多OO设计师声称你不需要MI,如果你使用它,你的模型可能是错误的。我不确定我是否同意这一点,但保持简单总是一个好计划。

答案 5 :(得分:8)

多重继承

  • 难以理解
  • 难以调试(例如,如果您将来自多个具有相同命名方法的框架的类混合在一起,可能会发生意想不到的协同效应)
  • 容易误用
  • 并非有用
  • 难以实施,尤其是如果您希望正确完成

因此,将多重继承包含在Java语言中是明智的选择。

答案 6 :(得分:8)

在C ++中,如果使用不当,多重继承是一个令人头痛的问题。为了避免那些流行的设计问题,在现代语言(java,C#)中强制使用多个接口“继承”。

答案 7 :(得分:5)

另一个原因是单继承使得转换变得微不足道,没有发出汇编指令(除了检查所需类型的兼容性之外)。如果你有多重继承,你需要弄清楚某个父类在子类中的位置。所以表现肯定是一种振作(虽然不是唯一的)。

答案 8 :(得分:4)

回到过去(70年代),当计算机科学更多的是科学和更少的大规模生产时,程序员有时间考虑良好的设计和良好的实施,因此产品(程序)具有高质量(例如TCP) / IP设计和实施)。 如今,当每个人都在编程,并且管理人员在截止日期前更改规范时,像Steve Haigh帖子在维基百科链接中描述的那些微妙问题很难跟踪;因此,“多重继承”受编译器设计的限制。如果你喜欢它,你仍然可以使用C ++ ....并拥有你想要的所有自由:)

答案 9 :(得分:4)

我接受了“Java中不允许多重继承”的陈述,并带有一点点盐。

当“类型”继承自多个“类型”时,会定义多重继承。并且接口也被分类为具有行为的类型。所以Java确实有多重继承。只是它更安全。

答案 10 :(得分:4)

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

在java中,实际上他们通过使用单继承和接口来避免多重继承的复杂性。 在下面解释的情况下,多重继承的复杂性非常高

多重继承的钻石问题。 我们有两个继承自A的B和C类。假设B和C重写了一个继承的方法,它们提供了自己的实现。现在D继承了B和C进行多重继承。 D应该继承那个重写方法,jvm无法决定使用哪个重写方法?

在c ++中,虚函数用于处理,我们必须明确地执行。

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

答案 11 :(得分:3)

如果继承的类具有相同的功能,实际上会出现多重继承的复杂性。即编译器会产生混淆,必须选择(钻石问题)。所以在Java中,复杂性被移除并提供接口以获得多重继承所提供的功能。我们可以使用界面

答案 12 :(得分:2)

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

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

目前的Java API功能无法做出这些决定。所以java中不允许多重继承。

答案 13 :(得分:1)

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

原因:

多重继承:引入更多复杂性和歧义。

接口:接口是Java中的完全抽象类,它为您提供了一种统一的方法,可以从其公共可用界面正确描绘程序的结构或内部工作方式,结果是更大的数量灵活性和可重用代码,以及对如何创建和与其他类交互的更多控制。

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

让我们举一个简单的例子。

  1. 假设有2个超类A和B类具有相同的方法名但功能不同。通过以下代码与(extends)关键字进行多重继承是不可能的。

    mpf_mul
  2. 但是通过接口,可以使用(implements)关键字进行多重继承。

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

答案 14 :(得分:0)

  

有人可以准确地告诉我为什么不允许这样做吗?

您可以在此文档link

中找到答案
  

Java编程语言不允许扩展多个类的一个原因是避免了多重继承状态的问题,即从多个类继承字段的能力

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

  1. 如果来自不同超类的方法或构造函数实例化相同的字段会怎样?

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

  3. 即使现在允许多重继承状态,仍然可以实现

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

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

    有关其他信息,请参阅此相关的SE问题:

    Multiple Inheritance Ambiguity with Interface

答案 15 :(得分:0)

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

但是,

C#和Java将类限制为单个继承,每个类都继承 来自单个父类。

多重继承是创建结合了两个不同类的方面的类的有用方法 层次结构,在单个内部使用不同的类框架时经常发生的事情 应用。

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

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

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 D来自class Bclass C都从A派生而来,因此本质上flag的两个副本可用,因为两个 A的实例在D的类层次结构中。您要设定哪一个?编译器会抱怨 flag中对D的引用是模糊的。一种解决方法是明确消除引用的歧义:

B::flag = nflag;

另一种解决方法是将B和C声明为virtual base classes,这意味着A的一个副本可以 存在于层次结构中,消除了任何歧义。

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

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

答案 16 :(得分:-3)

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

它有CalcualteArea方法:

Class Shape1
{

 public void CalculateArea()

     {
       //
     }
}

还有另一个类Shape2,其中一个也有相同的方法

Class Shape2
{

 public void CalculateArea()

     {

     }
}

现在我有一个子类Circle,它派生自Shape1和Shape2;

public class Circle: Shape1, Shape2
{
}

现在当我为Circle创建对象并调用该方法时,系统不知道要调用哪个计算区域方法。两者都有相同的签名。所以编译器会变得混乱。这就是为什么不允许多重继承。

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