为什么Java中没有多重继承,但是允许实现多个接口?


153

Java不允许多重继承,但是它允许实现多个接口。为什么?


1
我编辑了问题标题以使其更具描述性。
博佐2010年

4
有趣的是,在JDK 8中,将存在扩展方法,这些扩展方法将允许定义接口方法的实现。规则被定义为管理行为的多重继承,但不能的状态(我的理解是更成问题
埃德温Dalorzo

1
在你们把时间浪费在只告诉您“ java如何实现多重继承”的答案上之前……我建议您使用@Prabal Srivastava答案,从逻辑上讲,您必须在内部了解发生了什么,而不是让类正确。 。并且只允许接口拥有允许多重继承的权利。
Yo Apps

Answers:


228

因为接口只指定什么类是做,而不是如何它是做什么的。

多重继承的问题在于,两个类可能定义做同一件事的不同方式,而子类无法选择该选择哪一个。


8
我曾经做过C ++,多次遇到相同的问题。最近,我读到有关Scala具有“特征”的东西,在我看来,这似乎介于“ C ++”和“ Java”的处事方式之间。
Niels Basjes,2010年

2
这样,您就可以避免“钻石问题”:en.wikipedia.org/wiki/Diamond_problem#The_diamond_problem
Nick L.

6
从Java 8开始,您可以定义两个相同的默认方法,每个接口一个。如果你要实现你的班上两个接口,你必须覆盖类本身这个方法,请参阅:docs.oracle.com/javase/tutorial/java/IandI/...
bobbel

6
这个答案不准确。问题不在于如何证明Java 8。在Java 8中,两个超级接口可以使用不同的实现声明相同的方法,但这不是问题,因为接口方法是virtual,因此您只需覆盖它们即可解决问题。真正的问题是关于不确定性属性,因为你不可能解决这种不确定性覆盖属性(属性不是虚拟的)。
Alex Oliveira

1
您所说的“属性”是什么意思?
博佐

96

我的一位大学讲师以这种方式向我解释了这一点:

假设我有一个班级,是一个烤面包机,另一个班级是,核弹。它们都可能具有“黑暗”设置。它们都具有on()方法。(一个有一个off(),另一个没有。)如果我想创建一个同时属于这两个子类的类……如您所见,这实际上是一个问题。 。

因此,主要问题之一是,如果您有两个父类,则它们可能具有相同功能的不同实现,或者可能有两个具有相同名称的不同功能,例如在我的讲师的示例中。然后,您必须确定子类将使用哪个子类。当然,有多种方法可以处理此问题(C ++可以这样做),但是Java的设计人员认为这会使事情变得过于复杂。

但是,通过一个接口,您正在描述类可以执行的操作,而不是借用另一个类的执行方法。与多个父类相比,多个接口引起需要解决的棘手冲突的可能性要小得多。


5
谢谢,NomeN。引用的来源是名叫Brendan Burns的博士生,他当时还是开放源代码Quake 2源代码库的维护者。去搞清楚。
语法

4
这个类比的问题在于,如果您要创建核弹和烤面包机的子类,则“核烤面包机”在使用时会爆炸。
3100年

20
如果有人决定混合使用核弹和烤面包机,那么他应该得到炸弹炸毁的表情。引述是谬误性的推理
2013年

1
相同的编程语言确实允许多个“接口”继承,因此该“调整”不适用。
curiousguy

24

因为即使您不能说“嘿,该方法看起来很有用,继承也会被过度使用,所以我也将扩展该类”。

public class MyGodClass extends AppDomainObject, HttpServlet, MouseAdapter, 
             AbstractTableModel, AbstractListModel, AbstractList, AbstractMap, ...

您能解释一下为什么说继承被过度使用了吗?创建神课正是我想要做的!我发现有很多人通过创建具有静态方法的“工具”类来解决单一继承问题。
邓肯·卡尔维特

9
@DuncanCalvert:不,您不想这样做,如果该代码需要维护,则不会。许多静态方法没有考虑到面向对象,但是过多的多重继承会更加糟糕,因为您完全无法了解在何处使用了什么代码以及概念上使用了什么类。两者都试图解决“如何在需要的地方使用此代码”的问题,但这是一个简单的短期问题。通过适当的OO设计解决的更艰巨的长期问题是“如何更改该代码而又不会以无法预料的方式在20个不同的地方中断程序?
Michael Borgwardt 2014年

2
@DuncanCalvert:您可以通过具有高内聚性和低耦合性的类来解决此问题,这意味着它们包含相互紧密交互的数据和代码,但仅通过小型,简单的公共API与程序的其余部分交互。然后,您可以根据该API而不是内部细节来考虑它们,这很重要,因为人们只能同时记住有限数量的细节。
Michael Borgwardt 2014年

18

这个问题的答案在于java编译器(构造函数链接)的内部工作。 如果我们看到Java编译器的内部工作原理:

public class Bank {
  public void printBankBalance(){
    System.out.println("10k");
  }
}
class SBI extends Bank{
 public void printBankBalance(){
    System.out.println("20k");
  }
}

编译后看起来像这样:

public class Bank {
  public Bank(){
   super();
  }
  public void printBankBalance(){
    System.out.println("10k");
  }
}
class SBI extends Bank {
 SBI(){
   super();
 }
 public void printBankBalance(){
    System.out.println("20k");
  }
}

当我们扩展类并创建它的对象时,一个构造函数链将运行到Object类。

上面的代码可以正常运行。但是如果我们有另一个Car扩展类,Bank以及一个混合(多重继承)类,则SBICar

class Car extends Bank {
  Car() {
    super();
  }
  public void run(){
    System.out.println("99Km/h");
  }
}
class SBICar extends Bank, Car {
  SBICar() {
    super(); //NOTE: compile time ambiguity.
  }
  public void run() {
    System.out.println("99Km/h");
  }
  public void printBankBalance(){
    System.out.println("20k");
  }
}

在这种情况下,(SBICar)将无法创建构造函数链(编译时歧义)。

对于接口,这是允许的,因为我们无法创建它的对象。

对于新的概念defaultstatic方法,请参考interface中的default

希望这能解决您的查询。谢谢。


7

实现多个接口非常有用,并且不会对语言实现者或程序员造成太多问题。因此是允许的。多重继承虽然也很有用,但会给用户带来严重的问题(可怕的死亡钻石)。而且,您对多重继承所做的大多数事情也可以通过组合或使用内部类来完成。因此,禁止多重继承带来的问题多于收益。


“死亡钻石”有什么问题?
curiousguy

2
@curiousguy包含多个同一个基类的子对象,含糊不清(覆盖基类的使用),解决此类含糊不清的复杂规则。
塔德乌斯·科佩克

@curiousguy:如果框架规定将对象引用强制转换为基本类型引用将保留身份,则每个对象实例都必须具有任何基本类方法的一个实现。如果ToyotaCarHybridCar都派生自Car和覆盖Car.Drive,并且如果PriusCar两者都继承但未被覆盖Drive,则系统将无法识别虚拟机Car.Drive应该做什么。接口通过避免上面的斜体字形条件来避免此问题。
超级猫

1
@supercat“系统将无法识别虚拟Car.Drive应该做什么。” <-或者它可能会给出一个编译错误,使您像C ++一样明确地选择一个。
克里斯·米德尔顿

1
@ChrisMiddleton:一种方法void UseCar(Car &foo);不能期望包含ToyotaCar::Drive和之间的歧义HybridCar::Drive(因为通常不应该知道也不关心那些其他类型甚至存在)。像C ++一样,一种语言可能ToyotaCar &myCar希望希望将其传递给的代码UseCar必须首先强制转换为HybridCaror或ToyotaCar,但由于((Car)(HybridCar)myCar).Drive`并((Car)(ToyotaCar)myCar).Drive 会做不同的事情,这暗示了高调不保留身份。
2015年

6

您可以在oracle文档页面中找到有关多继承的此查询的准确答案。

  1. 状态的多重继承: 能够从多个类继承字段

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

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

    1. 如果来自不同超类的方法或构造函数实例化同一字段怎么办?
    2. 哪个方法或构造函数优先?
  2. 实现的多重继承:能够从多个类继承方法定义

    这种方法的问题:名称冲突含糊不清。如果子类和超类包含相同的方法名称(和签名),则编译器无法确定要调用的版本。

    但是java使用默认方法来支持这种类型的多重继承,这是自Java 8发布以来引入的。Java编译器提供了一些规则来确定特定类使用哪种默认方法。

    有关解决钻石问题的更多详细信息,请参阅下面的SE帖子:

    Java 8中抽象类和接口之间有什么区别?

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

    由于接口不包含可变字段,因此您不必担心这里的状态多重继承引起的问题。



4

Java仅通过接口支持多重继承。一个类可以实现任意数量的接口,但只能扩展一个类。

不支持多重继承,因为它会导致致命的钻石问题。但是,它可以解决,但会导致系统复杂,因此Java创始人放弃了多重继承。

James Gosling在1995年2月发表的题为“ Java:概述”的白皮书中(链接)给出了一个为什么Java不支持多重继承的想法。

根据高斯林的说法:

“ JAVA遗漏了许多C ++很少使用,理解不清,令人困惑的功能,在我们的经验中,它们带来的痛苦大于好处。这主要包括运算符重载(尽管它确实有方法重载),多重继承和广泛的自动强制。”


链接不可访问。请检查并更新它。
MashukKhan

3

出于相同的原因,C#不允许多重继承,但允许您实现多个接口。

从具有多重继承的C ++中学到的教训是,它导致了更多的问题,而不是值得的。

接口是您的类必须实现的事物的约定。您不会从该界面获得任何功能。继承使您可以继承父类的功能(在多重继承中,这可能会造成极大的混乱)。

允许多个接口使您可以使用设计模式(例如Adapter)来解决可以使用多重继承解决的相同类型的问题,但是要更加可靠和可预测。


10
C#没有多重继承正是因为Java不允许它。它的设计比Java晚得多。我认为多重继承的主要问题是人们被左右左右使用的方式。在大多数情况下,在九十年代初期和中期,并不存在授权是一个更好的选择的概念。因此,我记得教科书中的例子,当汽车是一个轮子,一个门和一个挡风玻璃,而汽车是一个轮子,门和挡风玻璃。因此,Java中的单一继承就是对这种现实的愚蠢反应。
Alexander Pogrebnyak,2010年

2
@AlexanderPogrebnyak:从以下三个中选择两个:(1)允许从子类型引用到超类型引用的保留身份的强制转换;(2)允许类添加虚拟公共成员,而无需重新编译派生类;(3)允许一个类从多个基类隐式继承虚拟成员,而不必显式指定它们。我认为任何一种语言都不可能同时管理以上三种。Java选择了#1和#2,C#也效仿。我相信C ++仅采用#3。我个人认为#1和#2比#3更有用,但其他人可能有所不同。
2013年

@supercat“我不相信任何一种语言都可以管理上述三种语言” –如果数据成员的偏移量是在运行时而不是编译时确定的(就像在Objective-C的“非脆弱ABI中一样”) ”,或基于每个类(即每个具体类都有其自己的成员偏移表),那么我认为可以实现所有3个目标。
顺磁牛角包

@TheParamagneticCroissant:主要的语义问题是#1。如果D1D2这两种继承B,每个覆盖的功能f,如果obj是一个类型的实例S,其来自继承D1D2,但不会覆盖f,然后铸造参考S,以D1应得的东西,其f用途的D1覆盖,并进行铸造而B不能改变它。同样铸造了参考S,以D2应得到的东西,其f用途的D2覆盖,并铸造B不应改变。如果一种语言不需要允许添加虚拟成员...
supercat

1
@TheParamagneticCroissant:可以,而且我的选择列表有些过分简化以适合评论,但由于延迟了运行时间,D1,D2或S的作者无法知道他们可以进行哪些更改而不会中断同类消费者。
supercat 2015年

2

由于本主题尚未结束,因此我将发布此答案,希望这有助于某人理解为什么Java不允许多重继承。

考虑以下类别:

public class Abc{

    public void doSomething(){

    }

}

在这种情况下,Abc类不会扩展任何内容,对吗?没那么快,该类隐式扩展了Object类,它是允许所有工作在Java中的基类。一切都是对象。

如果您尝试使用类上面,你会看到你的IDE允许您使用类似的方法:equals(Object o)toString(),等等,但你没有申报这些方法,他们就从基类Object

您可以尝试:

public class Abc extends String{

    public void doSomething(){

    }

}

很好,因为您的类不会隐式扩展,Object但会String因为您说的而扩展。请考虑以下更改:

public class Abc{

    public void doSomething(){

    }

    @Override
    public String toString(){
        return "hello";
    }

}

现在,如果您调用toString(),您的类将始终返回“ hello”。

现在想象一下下面的类:

public class Flyer{

    public void makeFly(){

    }

}

public class Bird extends Abc, Flyer{

    public void doAnotherThing(){

    }

}

再次,类Flyer隐式扩展了Object,它具有方法toString(),任何类都将具有此方法,因为它们都是Object间接扩展的,那么,如果toString()从中调用Bird,则toString()必须使用哪个java?来自Abc还是Flyer?任何尝试扩展两个或多个类的类都将发生这种情况,为了避免这种“方法冲突”,他们建立了接口,基本上您可以将它们视为不间接扩展Object抽象类。由于它们是抽象的,因此必须由一个类(即一个对象)来实现 (您不能单独实例化一个接口,它们必须由一个类来实现),因此一切都将继续正常运行。

为了区别于接口的类,只为接口保留关键字Implements

您可以在同一个类中实现您喜欢的任何接口,因为它们默认情况下不会扩展任何内容(但是您可以创建一个扩展另一个接口的接口,但是同样,“父”接口不会扩展Object”),所以一个接口是只是一个接口,它们将不会遭受“ 方法签名大肠菌 ”的困扰,如果这样做,编译器将向您发出警告,您只需要更改方法签名即可对其进行修复(签名=方法名称+参数+返回类型) 。

public interface Flyer{

    public void makeFly(); // <- method without implementation

}

public class Bird extends Abc implements Flyer{

    public void doAnotherThing(){

    }

    @Override
    public void makeFly(){ // <- implementation of Flyer interface

    }

    // Flyer does not have toString() method or any method from class Object, 
    // no method signature collision will happen here

}

1

因为接口只是合同。类实际上是数据的容器。


多重继承的基本困难是,一个类可能会通过多个路径继承一个成员,而这些路径以不同的方式实现了该成员,而没有提供自己的重写实现。接口继承避免了这种情况,因为可以实现的唯一地方接口成员是在类中,其后代将限于单一继承。
超级猫

1

例如,两个具有相同方法m1()的A,B类。C类同时扩展了A,B。

 class C extends A, B // for explaining purpose.

现在,类C将搜索m1的定义。首先,如果找不到,它将在班级搜索,然后它将检查到父母班级。A,B都具有定义,因此在这里出现歧义,应该选择哪个定义。因此,JAVA不支持多重继承。


如果在两个父类中定义了相同的方法或变量,那么如何使java编译器提供给编译器,以便我们可以更改代码...
siluveru kiran kumar,

1

Java不支持多重继承,原因有两个:

  1. 在Java中,每个类都是 Object级。当子类从多个超类继承时,子类将难以获得Object类的属性。
  2. 在Java中,如果我们显式编写或根本不编写,则每个类都有一个构造函数。第一条语句正在调用super()以调用晚饭类构造函数。如果该类具有多个超类,则会感到困惑。

因此,当一个类从一个以上的超类扩展而来时,我们会得到编译时错误。


0

以这种情况为例:类A有一个getSomething方法,类B有一个getSomething方法,而类C扩展了A和B。如果有人叫C.getSomething,会发生什么?无法确定要调用的方法。

接口基本上只是指定实现类需要包含哪些方法。实现多个接口的类仅意味着该类必须实现所有这些接口中的方法。如上所述,不会导致任何问题。


2
如果有人叫C.getSomething,会发生什么。 ”这是C ++中的错误。问题解决了。
curiousguy

就是这一点……这是我认为很清楚的反例。我指出,没有办法确定应该调用哪个方法。另外请注意,该问题与Java非c ++有关
John Kane

对不起,我不明白你的意思。显然,在某些情况下,MI存在歧义。这个反例如何?谁声称MI永远不会导致歧义?“ 另外,问题是与Java有关,而不与c ++有关 ”那么?
curiousguy

我只是想说明为什么存在歧义性,为什么它与接口不存在。
约翰·凯恩

是的,MI可能导致模棱两可的呼叫。重载也可以。Java应该消除重载吗?
curiousguy

0

考虑一个场景,其中Test1,Test2和Test3是三个类。Test3类继承了Test2和Test1类。如果Test1和Test2类具有相同的方法,并且您从子类对象调用它,则将有歧义地调用Test1或Test2类的方法,但是对于接口则没有歧义,因为在接口中没有实现。


0

由于不明确的问题,Java不支持多重继承,多路径和混合继承:

 Scenario for multiple inheritance: Let us take class A , class B , class C. class A has alphabet(); method , class B has also alphabet(); method. Now class C extends A, B and we are creating object to the subclass i.e., class C , so  C ob = new C(); Then if you want call those methods ob.alphabet(); which class method takes ? is class A method or class B method ?  So in the JVM level ambiguity problem occurred. Thus Java does not support multiple inheritance.

多重继承

参考链接: https : //plus.google.com/u/0/communities/102217496457095083679


0

我们都以简单的方式知道,我们可以继承(扩展)一个类,但是我们可以实现这么多的接口。这是因为在接口中,我们没有给出实现只是说功能。假设Java可以扩展这么多的类并且那些类具有相同的方法..在这一点上,如果我们尝试在子类中调用超类方法,该方法假设要运行?编译器会产生混淆的 示例:-尝试多次扩展, 但是在接口那些方法没有主体,我们应该在子类中实现它们。. 尝试使用多个实现, 因此没有后顾之忧。


1
您可以在此处发布这些示例。没有它,这看起来像是一块文本,对9岁的问题回答了一百次。
Pochmurnik

-1

*这是一个简单的答案,因为我是Java的初学者*

考虑到有三个类XY并且Z

因此,我们继承喜欢X extends Y, Z 而且两者YZ是有一个方法alphabet()具有相同的返回类型和参数。该方法alphabet()Y显示第一个字母,而方法在Z显示最后一个字母。因此,当alphabet()由调用时,这里会产生歧义X。它说显示的是一个字母还是最后一个字母???因此,java不支持多重继承。如果是接口,请考虑YZ视为接口。因此,两者都将包含方法的声明,alphabet()而不包含定义。它不会告诉您显示第一个字母还是最后一个字母或其他任何内容,而只是声明一个方法alphabet()。因此,没有理由提高歧义性。我们可以在类中使用任何我们想定义的方法X

总之,接口定义是在实现后完成的,因此不会造成混淆。

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.