多重继承和多重接口。它们之间真正的区别是什么?

4

我在网上阅读关于为什么Java不允许多重继承的原因,并且以下面例子来说明:

class A { 
   public void doSomething() { 
   } 
} 

class B { 
     public void doSomething() { 
     } 
} 

class C extends A,B { 

} 

public static void main(String args) { 
     C c = new C(); 
     c.doSoemthing();   // compiler doesnt know which doSeomthing to call.

上面的例子展示了我们所谓的钻石问题,即两个父类都拥有相同的方法名称。当一个子类尝试调用该方法时,编译器会感到困惑。
我的问题是,接口如何解决这种问题?

3
界面并不能解决这个问题,而是防止这种情况发生。 - Luiggi Mendoza
1
有关多重继承,默认方法和Java 8的有趣阅读:http://www.lambdafaq.org/what-about-the-diamond-problem/ - assylias
+1 @luiggi mendonza,那就是确切的答案。 - AllTooSir
另一种看待它的方式是尝试重写JLS,特别是第15.12节方法调用表达式,以展示处理多继承实现的规则。 - Patricia Shanahan
感谢大家提供的精彩答案。 - Stranger
由于接口只声明而不定义方法,因此编译器在方法被调用时并不会感到困惑,会直接调用你的类实现的方法。 - arynaq
7个回答

2

让我们来思考以下代码

interface P { 
   public void doSomething();
} 

interface Q { 
     public void doSomething();
} 
class A { 
   public void doSomething() { 
   } 
} 

class B { 
     public void doSomething() { 
     } 
} 

class C implements P,Q { 
     public void doSomething(){
         // implementation
     }
}

class D extends A,B {   // suppose it is possible


}

现在要使用C对象,您需要在C类中实现doSomething()方法。该方法仅在C类中实现。

但是,如果您可以创建D的对象并调用doSomething,应该调用哪个方法?因为此方法在A和B中均已实现。

菱形问题

实际上,真正的菱形问题是

class A { 
   public void doSomething() { 
   } 
} 

class B extends A{ 
     public void doSomething() { 
     } 
} 

class C extends A{ 
     public void doSomething() { 
     } 
} 


class D extends B,C {   // suppose it is possible
   // no implementation of doSomething.
}

这里插入图片描述

因为它的形状像钻石,所以被称为Diamond。如果您想要进行以下操作:

D d = new D();
d.doSomething(); // which method should be called now????

维基百科这里有一个很好的实时例子:

例如,在GUI软件开发的上下文中,一个名为Button的类既可以从Rectangle类(用于外观)继承,也可以从Clickable类(用于功能/输入处理)继承。而Rectangle和Clickable两个类都继承自Object类。现在,如果对Button对象调用equals方法,但是Button类中没有这样的方法,而Rectangle和Clickable中都有重写的equals方法,那么最终应该调用哪个方法呢?


2

2
这个链接的日期可以追溯到2000年,但它是关于这个主题的一个很好的例子。 - Luiggi Mendoza
是的,那很老但很有价值 :)。 - Suresh Atta

1
界面没有实现doSomething()方法,因此您无法调用界面方法。界面只是一个签名,告诉实际(实现)类要实现哪些方法。您将在类C中实现doSomething(),这将是您在调用B.doSomething()A.doSomething()时调用的方法。
在扩展两个具有两个doSomething()方法的类的情况下,它们可以具有不同的实现,您将不知道调用哪个方法。看这个例子:
class A { 
   public void doSomething() { 
       System.out.println("A");
   } 
} 

class B { 
   public void doSomething() { 
       System.out.println("B");
   } 
} 

class C extends A & B { //if this would be an option

} 

public static void main(String args) { 
    C c = new C(); 
    c.doSoemthing();   //Print "A" or "B" ???
}

结论:这是一个实现的问题。接口不为任何方法提供任何实现,因此可以安全地从具有相同方法签名的接口继承。

0

这两个类都提供了JVM在调用时可以跳转的代码。这就是歧义所在。属性也有同样的问题,编译器可能会有两个具有相同名称的属性需要查找,这将产生类似的歧义。

接口不会提供该代码。因此不会有冲突。

其他支持多重继承的语言在出现这些歧义时会让编译器禁止它们。并且需要进行特殊处理来解决它们。

class C{
    public void doSomething(){   
       // Call  (this inferred)
       B.doSomething();
       // leave A.doSomething() alone. 
    }
}

0

多重继承可以从许多类中继承成员数据以及它们的所有函数。多个接口只能继承函数原型,并且它们必须由子类实现


1
Java没有函数,而是使用方法。并且你不会继承方法,类的实现者需要定义方法的行为。 - Luiggi Mendoza

0
一个接口没有方法的实现,因此它们将被合并为同一个方法。 事实上,你的对象承诺实现一个名为doSomething的方法,但不具体绑定到某个接口(两者都适用)。

0

答案就在你的问题中。对于接口,编译器不会感到困惑,因为在接口中没有实现。它是你的具体类提供实际的实现。因此没有混淆。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接