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


115

我知道Java和C#中不允许多重继承。许多书只是说,不允许多重继承。但是可以通过使用接口来实现。没有任何关于为什么不允许它的讨论。谁能确切告诉我为什么不允许这样做?


1
就像要指出的那样,有一些框架允许C#类中的MI行为。当然,您也可以选择使用无状态混合(使用扩展方法)-无状态混合并不是很有用。
Dmitri Nesteruk 2009年

1
接口与继承有关是语言语法造成的一种幻觉。继承应该使您变得更加富有。界面不像那样,您不会继承下蹲,这会使您变得更加贫穷。您继承了哈利叔叔的赌博债,还需要做更多的工作来实现该界面。
汉斯·帕桑

Answers:


143

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

基本上,.NET和Java设计器似乎都不允许多重继承,因为他们认为添加MI会增加语言的复杂性,而带来的好处却很少

要获得更有趣和深入的阅读,Web上提供了一些文章,并对一些语言设计师进行了采访。例如,对于.NET,克里斯·布鲁姆(Chris Brumme)(曾在CLR的MS上工作)解释了他们决定不这样做的原因:

  1. 实际上,不同的语言对MI的工作方式有不同的期望。例如,如何解决冲突以及重复的碱基是合并的还是冗余的。在CLR中实现MI之前,我们必须对所有语言进行调查,弄清楚常见的概念,并决定如何以与语言无关的方式来表达它们。我们还必须确定MI是否属于CLS,以及对于不希望使用此概念的语言(例如,大概是VB.NET)意味着什么。当然,这是我们作为公共语言运行时所从事的业务,但是我们还没有为MI做这件事。

  2. 实际上,真正适合使用MI的地方数量很少。在许多情况下,多接口继承可以代替工作。在其他情况下,您可能可以使用封装和委派。如果我们要添加一个稍微不同的结构(如mixin),实际上会更强大吗?

  3. 多重实现继承为实现注入了很多复杂性。这种复杂性会影响投射,布局,调度,现场访问,序列化,身份比较,可验证性,反射,泛型以及可能还有很多其他地方。

您可以在此处阅读全文。

对于Java,您可以阅读本文

从Java语言中省略多个继承的原因主要来自“简单,面向对象和熟悉的”目标。作为一种简单的语言,Java的创建者想要一种大多数开发人员无需大量培训即可掌握的语言。为此,他们努力使该语言尽可能类似于C ++(熟悉),而又不承担C ++不必要的复杂性(简单)。

在设计人员看来,多重继承会带来更多无法解决的问题和混乱。因此,他们削减了语言的多重继承(就像削减了运算符的重载一样)。设计师丰富的C ++经验告诉他们,多重继承根本不值得头疼。


10
不错的比较。我认为这两个平台背后的思维过程非常充分。
CurtainDog

97

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

问题是,如果您有一个Cowboy和Artist类(都具有draw()方法的实现),然后尝试创建新的CowboyArtist类型,则编译器/运行时无法弄清楚该怎么办。调用draw()方法会怎样?有人死在街上吗,还是您有可爱的水彩画?

我相信这就是双重钻石继承问题。


80
您会看到死在大街上的人的可爱水彩画:-)
Dan F

这是唯一的问题吗?我认为,我不确定C ++是否可以通过virtual关键字解决此问题,这是真的吗?我不太擅长C ++
Abdulsattar Mohammed

2
在C ++中,您可以指定在派生中调用哪个基类函数,也可以自己重新实现它。
德米特里·里森贝格

1
在最简单的情况下,现代设计在所有情况下都倾向于使用组合而不是继承。多重继承永远不会被视为简单的案例。通过合成,您可以精确地控制班级在出现诸如此类的钻石问题时的工作...
比尔·米歇尔

您只需要一个优先级列表。这在Common Lisp的对象系统CLOS中使用。所有类都构成一个杂乱无章的事物,从那里确定所调用的方法。此外,CLOS允许您定义不同的规则,以便CowboyArtist.draw()可以先绘制牛仔,然后再绘制Artist。或是您组成的。
塞巴斯蒂安·格罗格

19

原因: Java非常简单,因此非常流行并且易于编码。

因此,对于Java开发人员来说,对于程序员来说,理解起来既困难又复杂,他们都试图避免这种情况。这种属性之一就是多重继承。

  1. 他们避免了指针
  2. 他们避免了多重继承。

多继承问题钻石问题。

范例

  1. 假定类A具有方法fun()。B类和C类来自A类。
  2. 并且类B和C都覆盖了fun()方法。
  3. 现在假设类D继承了类B和类C。
  4. 为类D创建对象。
  5. D d = new D();
  6. 并尝试访问d.fun(); =>它会调用B类的fun()还是C类的fun()?

这是钻石问题中存在的歧义。

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

注意:但是,无论如何,您始终可以通过使用接口间接实现多重继承。


1
“但是,您始终可以通过使用接口间接实现多重继承的任何方式。” -要求程序员一次又一次地重新指定方法的主体。
卡里

13

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

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

Java设计哲学是强调语言构造中的安全性。结果是有些事情要做起来很尴尬,但是您可以更加确信所查看的代码意味着您所认为的是正确的。

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

更不用说完全有可能使用接口,组合和委派在Java中进行MI。它比C ++更明确,因此使用起来比较笨拙,但乍一看会为您带来一些您更可能理解的知识。

这里没有正确的答案。有不同的答案,在给定情况下哪种更好取决于应用程序和个人偏好。


12

人们偏离MI的主要原因(尽管绝不是唯一的原因)是所谓的“钻石问题”,导致您的实现方式含糊不清。这篇维基百科文章对此进行了讨论,并且比我能更好地解释。MI也可能导致更复杂的代码,许多OO设计人员声称您不需要MI,如果您使用MI,则您的模型可能是错误的。我不确定我是否同意最后一点,但保持简单始终是一个好计划。


8

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


8

多重继承为

  • 很难理解
  • 难以调试(例如,如果您将多个框架中的类混合使用,而这些类在内部具有完全相同名称的方法,则可能会发生非常意外的协同作用)
  • 容易误用
  • 不是真的有用的
  • 难以实现,特别是如果你想要做正确有效

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


3
在使用Common Lisp对象系统时,我不同意上述所有观点。
David Thornley,2009年

2
动态语言不计算在内;-)在任何类似Lisp的系统中,您都有一个REPL,它使调试变得相当容易。而且,CLOS(据我所知,我从未真正使用过它,只读过它)是一个元对象系统,具有很大的灵活性和自用的态度。但是考虑一下像C ++这样的静态编译语言,在该语言中,编译器使用多个(可能是重叠的)vtable生成了一些非常复杂的方法查找:在这种实现中,找出调用方法的哪种实现可能不是一件容易的事。
mfx

5

另一个原因是单继承使转换变得无关紧要,不发出汇编指令(除了在需要时检查类型的兼容性之外)。如果您有多重继承,则需要确定某个父级在子类中的哪个位置开始。因此,性能肯定是一种福利(尽管不是唯一一种)。


4

早在上世纪70年代,计算机科学成为一门科学,而批量生产却少了,程序员有时间去考虑良好的设计和良好的实现,因此产品(程序)的质量很高(例如TCP / IP设计)。和实施)。如今,当每个人都在编程时,管理人员在截止日期之前更改规范时,很难跟踪到像Steve Haigh帖子中的Wikipedia链接中所述的那些细微问题。因此,“多重继承”受到编译器设计的限制。如果喜欢,您仍然可以使用C ++ ....,并拥有所有想要的自由:)


4
...包括多次自由射击自己的脚;)
MarioOrtegón2009年

4

我发表了这样的声明:“在Java中不允许多重继承”。

当“类型”从多个“类型”继承时,定义了多重继承。并且接口由于具有行为也被归类为类型。因此Java确实具有多重继承。只是更安全。


6
但是您不能从接口继承,而是要实现它。接口不是类。
Blorgbeard将于2009年

2
是的,但是除了一些入门书籍以外,对继承的定义从未如此狭窄。我认为我们应该超越语法的范围。他们都做同样的事情,并且接口是“发明”的唯一原因。
里格·韦达

采取接口Closeable:一种方法,close()。假设您想将其扩展到Openable:它具有open()方法,该方法将布尔字段isOpen设置为true。试图打开一个已经打开的Openable会引发一个异常,就像试图关闭一个尚未打开的Openable一样……数百种更有趣的类谱系可能希望采用这种功能……而不必选择扩展每个类Openable类的时间。如果Openable只是一个接口,则无法提供此功能。QED:接口不提供继承!
麦克啮齿动物

4

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

实际上,在Java中,他们通过使用单继承和接口避免了多重继承的复杂性。在如下所述的情况下,多重继承的复杂性非常高

钻石多重继承问题。 我们有两个类B和C继承自A。假定B和C覆盖了继承的方法,并且它们提供了自己的实现。现在D继承了B和C的多重继承。D应该继承该重写的方法,jvm无法确定将使用哪个重写的方法?

在c ++中,使用虚函数进行处理,我们必须显式地进行处理。

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


3

实际上,如果继承的类具有相同的功能,则多重继承会带来复杂性。也就是说,编译器会有一个必须选择的混乱(钻石问题)。因此,在Java中,这种复杂性得以消除,并提供了接口来获得像多重继承那样的功能。我们可以使用界面


2

Java具有概念,即多态。Java中有2种类型的多态性。有方法重载和方法重载。其中,方法重写发生在父类和子类之间。如果我们要创建一个子类的对象并调用超类的方法,并且如果子类扩展了多个类,则应调用哪种超类方法?

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

当前的Java API功能无法做出此决定。因此在Java中不允许多重继承。


从根本上讲,Java的类型系统假定每个对象实例都有一个类型,将对象强制转换为超类型将始终有效并且保留引用,如果实例属于该子类型或子对象,则将引用强制转换为子类型将保留引用。其子类型。这样的假设是有用的,并且我认为不可能以与它们一致的方式来允许广义多重继承。
2012年

1

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

原因:

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

接口:接口是Java中的完全抽象类,为您提供了一种统一的方法,可以从其公共可用接口正确地描述程序的结构或内部工作,从而带来更大的灵活性,可重用的代码以及更多的控制有关如何创建其他类并与之交互的信息。

更准确地说,它们是Java中的特殊构造,具有附加的特征,使您可以执行多种继承,即可以上载到多个类的类。

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

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

       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
            }
        }
  2. 但是通过接口,使用(实现)关键字可以实现多重继承。

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

0

谁能确切告诉我为什么不允许这样做?

您可以从此文档链接中找到答案

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

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

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

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

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

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

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

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

接口的多重继承歧义


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的类层次结构。您要设定哪一个?编译器会抱怨引用flagD模棱两可的。一种解决方法是明确消除引用的歧义:

B::flag = nflag;

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

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

尽管这确实大大简化了继承,但是由于只有具有共同祖先的类才能共享行为,因此它也限制了它的用途。接口通过允许不同层次结构中的类公开通用接口,即使它们不是通过共享代码实现的,也从某种程度上减轻了此限制。


-1

想象一下这个例子:我有一堂课 Shape1

它具有CalcualteArea方法:

Class Shape1
{

 public void CalculateArea()

     {
       //
     }
}

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

Class Shape2
{

 public void CalculateArea()

     {

     }
}

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

public class Circle: Shape1, Shape2
{
}

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

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


语言设计人员可以像在Interface中一样进行显式的方法调用。没有这一点!另一个原因是现在您如何考虑具有抽象方法的抽象类?
娜美·阿里
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.