提问者:小点点

Java8没有提供与解决接口默认方法相同的允许多重继承的解决方案


问题:

我们知道Java不允许扩展多个类,因为这会导致钻石问题,编译器无法决定使用哪个超类方法。对于接口默认方法,钻石问题是在Java8中引入的。也就是说,因为如果一个类实现了两个接口,每个接口定义了相同的默认方法,并且实现类没有覆盖通用的默认方法,编译器无法决定选择哪个实现。

解决方案:

Java8要求为多个接口实现的默认方法提供实现。因此,如果一个类要实现上面提到的两个接口,它必须为公共默认方法提供实现。否则编译器会抛出编译时错误。

问题:

为什么这个解决方案不适用于多类继承,通过覆盖子类引入的常用方法?


共3个答案

匿名用户

你没有正确理解钻石问题(当然,维基百科文章的当前状态并不能充分解释它)。如图所示,

当同一个类通过不同的继承路径被多次继承时,就会出现菱形问题。这对于接口来说不是问题(从来都不是),因为它们只定义一个合约,多次指定同一个合约没有区别。

主要问题不在于方法,而在于超级类型的数据。在这种情况下,A的实例状态应该存在一次还是两次?如果一次,CB可能对A的实例状态有不同的、相互冲突的约束。这两个类也可能假设完全控制A的状态,即不考虑具有相同访问级别的其他类。如果有两个不同的A状态,则D引用到A引用的扩大转换会变得模糊,因为A都可能意味着。

接口没有这些问题,因为它们根本不携带实例数据。它们也(几乎)没有可访问性问题,因为它们的方法始终是public。允许default方法不会改变这一点,因为default方法仍然不访问实例变量,而是仅使用接口方法进行操作。

当然,也有可能是BC用相同的签名声明了default方法,导致歧义,必须在D中解决。但即使是这样,当没有A时,即根本没有“钻石”。所以这种情况不是“钻石问题”的正确例子。

匿名用户

接口引入的方法可能总是被覆盖,而类引入的方法可能是最终的。这就是为什么您可能无法对类应用与接口相同的策略的原因之一。

匿名用户

描述为“菱形问题”的冲突可以用对方法A. m()的多态调用来最好地说明,其中接收者的运行时类型为D:想象一下D继承了两种不同的方法,它们都声称扮演A.m()的角色(其中一种可能是原始方法A.m(),其中至少一种是覆盖)。现在,动态调度无法决定调用哪些冲突方法。

旁白:“菱形问题”和常规名称冲突之间的区别在Eiffel等语言中尤其重要,在Eiffel中,冲突可以从D类型的角度本地解决,例如,通过重命名一个方法。这将避免静态类型D的调用的名称冲突,但不适用于静态类型A的调用。

现在,在Java8的默认方法中,JLS被修改为检测任何此类冲突的规则,要求D来解决冲突(存在许多不同的情况,这取决于所涉及的一些类型是否是类)。也就是说,钻石问题在Java8中没有“解决”,它只是通过拒绝任何会产生它的程序来避免。

理论上,类似的规则可以在Java1中定义为允许类的多重继承。这只是早期做出的决定,Java的设计者不想支持多重继承。

选择允许默认方法的多重(实现)继承但不允许类方法的多重(实现)继承是一个纯粹实用的选择,没有任何理论需要。