3

问题:

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

解决方案:

Java 8需要为由多个接口实现的默认方法提供实现。因此,如果一个类同时实现了上面提到的两个接口,它就必须为通用默认方法提供一个实现。否则编译器会抛出编译时错误。

问题:

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

4

3 回答 3

7

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

真正的钻石

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

主要问题与方法无关,而是与该超类型的数据相关。在这种情况下,实例状态应该A存在一次还是两次?如果一次,C并且可以对的实例状态B有不同的冲突约束。A这两个类也可能假设对 's 状态具有完全控制权A,即不认为其他类具有相同的访问级别。如果有两种不同A的状态,则将D引用扩大到A引用的转换会变得模棱两可,这A可能意味着。

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

当然,有可能BC声明的default方法具有相同的签名,导致歧义,必须在D. 但即使没有A,即根本没有“钻石”,情况也是如此。所以这个场景不是“钻石问题”的正确例子。

于 2016-12-21T09:58:08.980 回答
1

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

于 2016-12-21T09:29:48.610 回答
0

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

另外:“钻石问题”和常规名称冲突之间的区别在像 Eiffel 这样的语言中特别相关,其中冲突可以从 type 的角度在本地解决D,例如,通过重命名一种方法。这将避免静态类型调用的名称冲突D,但不会避免静态类型调用的名称冲突A

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

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

允许默认方法而不是类方法的多重(实现)继承的选择纯粹是务实的选择,任何理论都不需要。

于 2016-12-22T10:11:45.477 回答