我知道在 Java 和 C# 中不允许多重继承。很多书只是说,不允许多重继承。但它可以通过使用接口来实现。没有讨论为什么不允许这样做。谁能准确地告诉我为什么不允许这样做?
17 回答
简短的回答是:因为语言设计者决定不这样做。
基本上,似乎 .NET 和 Java 设计者都不允许多重继承,因为他们认为添加 MI 会增加语言的复杂性,而提供的好处却太少。
为了获得更有趣和更深入的阅读,网络上提供了一些文章,其中包含对一些语言设计师的采访。例如,对于 .NET,Chris Brumme(曾在 MS 从事 CLR 工作)解释了他们决定不这样做的原因:
不同的语言实际上对 MI 的工作方式有不同的期望。例如,如何解决冲突以及重复碱基是合并还是冗余。在我们可以在 CLR 中实现 MI 之前,我们必须对所有语言进行调查,找出共同的概念,并决定如何以与语言无关的方式来表达它们。我们还必须决定 MI 是否属于 CLS,以及这对于不想要这个概念的语言(例如 VB.NET)意味着什么。当然,这就是我们作为公共语言运行时所从事的业务,但我们还没有为 MI 做这件事。
真正适合 MI 的地方实际上很少。在许多情况下,多接口继承可以代替完成工作。在其他情况下,您可以使用封装和委托。如果我们要添加一个稍微不同的结构,比如 mixins,那实际上会更强大吗?
多重实现继承给实现注入了很多复杂性。这种复杂性会影响铸造、布局、调度、字段访问、序列化、身份比较、可验证性、反射、泛型,可能还有很多其他地方。
对于 Java,您可以阅读这篇文章:
从 Java 语言中省略多重继承的原因主要源于“简单、面向对象和熟悉”的目标。作为一种简单的语言,Java 的创建者想要一种大多数开发人员无需经过大量培训即可掌握的语言。为此,他们努力使该语言尽可能与 C++ 相似(熟悉),而不会继承 C++ 不必要的复杂性(简单)。
在设计者看来,多重继承带来的问题和混乱比它解决的要多。所以他们削减了语言的多重继承(就像他们削减了运算符重载一样)。设计者丰富的 C++ 经验告诉他们,多重继承根本不值得头疼。
实现的多重继承是不允许的。
问题是,如果你有一个 Cowboy 和一个 Artist 类,它们都具有 draw() 方法的实现,编译器/运行时无法弄清楚该怎么做,然后你尝试创建一个新的 CowboyArtist 类型。调用 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 在很大程度上是来自最著名的 OO 语言 C++ 和 Smalltalk 的反应。还有很多其他 OO 语言(Common Lisp 实际上是第一个被标准化的语言),不同的 OO 系统可以更好地处理 MI。
更不用说使用接口、组合和委托在 Java 中进行 MI 是完全可能的。它比 C++ 更明确,因此使用起来更笨拙,但会给你一些你一眼就能理解的东西。
这里没有正确的答案。有不同的答案,对于给定的情况,哪个更好取决于应用程序和个人偏好。
人们避开 MI 的主要(尽管绝不是唯一)原因是所谓的“钻石问题”,导致您的实施中存在歧义。这篇维基百科文章比我能更好地讨论它并解释得更好。MI 也可能导致更复杂的代码,许多 OO 设计人员声称您不需要 MI,如果您使用它,您的模型可能是错误的。我不确定我是否同意最后一点,但保持简单总是一个好计划。
在 C++ 中,如果使用不当,多重继承是一个令人头疼的问题。为了避免这些流行的设计问题,在现代语言(java、C#)中强制使用多个接口“继承”。
多重继承是
- 很难理解
- 难以调试(例如,如果您将多个框架中的类混合在一起,这些框架具有相同名称的方法,则可能会出现非常意想不到的协同作用)
- 容易误用
- 没那么有用
- 难以实施,特别是如果您希望正确有效地完成它
因此,在 Java 语言中不包含多重继承可以被认为是一个明智的选择。
另一个原因是单继承使强制转换变得微不足道,不发出汇编指令(除了在需要时检查类型的兼容性)。如果您有多重继承,则需要确定某个父级在子类中的哪个位置开始。所以性能肯定是一个额外的好处(尽管不是唯一的)。
早在过去(70 年代),当计算机科学更多是科学而大规模生产较少时,程序员有时间考虑良好的设计和良好的实现,因此产品(程序)具有高质量(例如 TCP/IP 设计)和实施)。如今,当每个人都在编程,并且经理们在截止日期之前更改规格时,难以追踪像史蒂夫黑格帖子中的维基百科链接中描述的微妙问题。因此,“多重继承”受到编译器设计的限制。如果你喜欢它,你仍然可以使用 C++ .... 并拥有你想要的所有自由 :)
我对“Java 中不允许多重继承”这一说法持怀疑态度。
当“类型”从多个“类型”继承时,定义了多重继承。接口也被归类为类型,因为它们具有行为。所以Java确实有多重继承。只是它更安全。
类的动态加载使多重继承的实现变得困难。
在java中,实际上他们通过使用单继承和接口来避免多重继承的复杂性。在下面解释的情况下,多重继承的复杂性非常高
多重继承的菱形问题。 我们有两个从 A 继承的 B 和 C 类。假设 B 和 C 覆盖了一个继承的方法,并且它们提供了自己的实现。现在 D 继承自 B 和 C 进行多重继承。D 应该继承那个被覆盖的方法,jvm 不能决定使用哪个被覆盖的方法?
在 C++ 中,虚函数用于处理,我们必须明确地做。
这可以通过使用接口来避免,没有方法体。接口不能被实例化——它们只能由类实现或由其他接口扩展。
实际上,如果继承的类具有相同的功能,则多重继承会产生复杂性。即编译器将混淆必须选择哪个(钻石问题)。因此,在 Java 中,复杂性消除了,并提供了接口来获得多重继承所提供的功能。我们可以使用接口
Java有概念,即多态。java中有两种类型的多态。有方法重载和方法覆盖。其中,方法覆盖发生在超类和子类关系中。如果我们正在创建一个子类的对象并调用超类的方法,并且如果子类扩展了多个类,那么应该调用哪个超类方法?
或者,在调用超类构造函数时,会调用super()
哪个超类构造函数?
当前的 java API 特性不可能做出这样的决定。所以java中不允许多重继承。
Java 中不允许直接进行多重继承,但允许通过接口进行多重继承。
原因 :
多重继承:引入了更多的复杂性和模糊性。
接口:接口是 Java 中的完全抽象类,它为您提供了一种统一的方法,可以从其公开可用的接口中正确地描述程序的结构或内部工作,其结果是更大的灵活性和可重用代码以及更多控制关于你如何创建和与其他类交互。
更准确地说,它们是 Java 中的一种特殊结构,具有允许您执行一种多重继承的附加特性,即可以向上转换为多个类的类。
让我们举个简单的例子。
假设有 2 个超类 A 和 B 具有相同的方法名称但功能不同。通过以下带有(扩展)关键字的代码,多重继承是不可能的。
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 } }
但是通过接口,使用(implements)关键字进行多重继承是可能的。
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 } }
谁能准确地告诉我为什么不允许这样做?
您可以从此文档链接中找到答案
Java 编程语言不允许您扩展多个类的一个原因是避免状态的多重继承问题,即从多个类继承字段的能力
如果允许多重继承,并且当您通过实例化该类来创建对象时,该对象将从该类的所有超类中继承字段。会导致两个问题。
如果来自不同超类的方法或构造函数实例化相同的字段怎么办?
哪个方法或构造函数将优先?
即使现在允许状态的多重继承,您仍然可以实现
类型的多重继承:一个类实现多个接口的能力。
实现的多重继承(通过接口中的默认方法):能够从多个类继承方法定义
有关其他信息,请参阅此相关 SE 问题:
在 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
}
};
In this example, the flag
data member is defined by class A
. But class D
descends from class B
and class C
, which both derive from A
, so in essence two copies of flag
are available because two
instances of A
are in D
’s class hierarchy. Which one do you want to set? The compiler will complain
that the reference to flag
in D
is ambiguous. One fix is to explicitly disambiguate the reference:
B::flag = nflag;
Another fix is to declare B and C as virtual base classes
, which means that only one copy of A can
exist in the hierarchy, eliminating any ambiguity.
Other complexities exist with multiple inheritance, such as the order in which the base classes are initialized when a derived object is constructed, or the way members can be inadvertently hidden from derived classes. To avoid these complexities, some languages restrict themselves to the simpler single inheritance model.
Although this does simplify inheritance considerably, it also limits its usefulness because only classes with a common ancestor can share behaviors. Interfaces mitigate this restriction somewhat by allowing classes in different hierarchies to expose common interfaces even if they’re not implemented by sharing code.
想象一下这个例子:我有一堂课Shape1
它有CalcualteArea
方法:
Class Shape1
{
public void CalculateArea()
{
//
}
}
还有另一个类Shape2
也有相同的方法
Class Shape2
{
public void CalculateArea()
{
}
}
现在我有一个子类 Circle,它派生自 Shape1 和 Shape2;
public class Circle: Shape1, Shape2
{
}
现在,当我为 Circle 创建对象并调用该方法时,系统不知道要调用哪个计算面积方法。两者都有相同的签名。所以编译器会感到困惑。这就是不允许多重继承的原因。
但是可以有多个接口,因为接口没有方法定义。即使两个接口都有相同的方法,它们都没有任何实现,并且总是会执行子类中的方法。