【问题标题】:Do interfaces solve the "deadly diamond of death" issue?接口是否解决了“致命的死亡钻石”问题?
【发布时间】:2012-03-25 14:14:14
【问题描述】:

接口是否解决了deadly diamond of death 问题?

我不这么认为,例如:

// A class implementing two interfaces Interface1 and Interface2.
// Interface1 has int x=10 and Interface2 has int x = 20

public class MultipleInterface implements Interface1, Interface2{

    public void getX(){
        System.out.println(x);
    }
}

这里我们得到一个模棱两可的x

虽然接口是解决方法歧义的好方法,但我猜它们在变量的情况下会失败?

我说的对吗?如果我遗漏了什么,请赐教。

【问题讨论】:

  • Java 总是有菱形,因为 Java 从第一天起就有多个 types 继承(接口 B 和 C 扩展 A,然后类 D 实现 B 和 C)。但这些钻石始终属于非致命品种。 Java 8 增加了 behavior 的多重继承,但这又不是致命的,因为它不可能继承同一方法的两个不同实现。

标签: java interface multiple-inheritance


【解决方案1】:

当一个类从父接口继承两个变量时,Java 坚持对所讨论的变量名的任何使用都是完全限定的。这解决了问题。见Java Language Specification Section 8.3

一个类可以继承多个同名字段。这种情况本身不会导致编译时错误。但是,在类的主体中通过其简单名称引用任何此类字段的任何尝试都将导致编译时错误,因为此类引用是不明确的。

类似的声明适用于接口 (JLS §9.3)。

the answer by Óscar López 中的示例代码非常棒。这是另一个例子:

class Base {
    int x = 10;
}

interface Interface {
    int x = 20;
}

class SingleInheritance implements Interface {
    int y = 2 * x; // ok
}

class MultipleInheritance extends Base implements Interface {
    int y = 2 * x; // compile-time error
    int z = 2 * Interface.x; // ok
}

void aMethod(MultipleInheritance  arg) {
    System.out.println("arg.x = " + arg.x); // compile-time error
    System.out.println("x = " + Interface.x); // ok
}

编辑

Java 8 为方法引入了一种有限形式的多重继承,因为接口现在可以声明子接口和实现类可以继承的default methods。由于一个类可以实现多个接口,这可能会导致歧义,因为具有相同签名的不同默认方法可以从多个接口继承。1Java 使用优先级方案来处理这个问题,以指定哪个默认方法实际上是遗传。当优先方案无法产生一个获胜者时,它需要显式覆盖继承的默认方法。

请注意,在任何情况下,Java 都不存在 Diamond 问题,它是多重继承问题的一个非常具体的子类。2 “Diamond”部分指的是类的形状出现问题所需的继承图。在 C++ 中,如果一个类 A 继承自两个类 B 和 C,每个类都继承自一个公共基类 D,就会出现菱形问题。在这种情况下,D 的任何公共成员最终会在 A 中出现两次——一次是通过B 一次通过 C。此外,每当 A 的实例被构造或销毁时,D 的构造函数或析构函数最终都会被调用两次(通常会带来灾难性的后果,因此名称中的“死亡”部分)。 C++ 通过提供虚拟继承解决了这些问题。 (详见讨论here。)

1 注意“不同”一词的使用。如果 same 默认方法是通过两个父接口继承的,这两个父接口又扩展了定义默认方法的公共基接口,则没有问题;默认方法是简单继承的。

2 其他多重继承问题——比如 Java 中可能出现的接口字段、静态方法和默认方法的歧义——在技术上与 Diamond 问题无关(实际上,致命钻石问题)。但是,有关该主题的许多文献(以及此答案的早期版本)最终将所有多重继承问题归为“死亡钻石”标题。我想这个名字太酷了,只能在技术上合适的时候使用。

【讨论】:

  • 你能用代码说明你的意思吗?据我所知,变量不能从接口继承。
  • 您能否评论一下您在关于 Java8 的“编辑”部分想象的具体问题场景? (仅供参考,此答案在this question 中引用。)
  • @TedHopp - 确实。我想我并不认为这是一个问题,因为编译器会强制你明确地消除歧义。
  • @TedHopp 在该部分中几乎所有关于 Java 的内容都是不正确的。
  • @BrianGoetz - 我重写了我的答案以消除 Java 存在 DoD 问题的建议。我相信名称的“死亡”部分来自 C++ 以及当创建或销毁菱形继承结构底部的后代类实例时多次调用基类构造函数或析构函数时可能发生的非常糟糕的事情。至少这方面的问题与状态的多重继承无关。
【解决方案2】:

接口不能有属性。当你写这个时:

public interface Foo {
    int x;
}

在后台,它隐式转换为常量,如下所示:

public interface Foo {
    public static final int x;
}

假设您有另一个具有类似名称常量的接口:

public interface Bar {
    int x;
}

如果您要在实现 FooBar 的类中使用 x 值,则必须限定这些常量,不能有歧义,如下所示:

public class Baz implements Foo, Bar {
    private int y = Foo.x + Bar.x;
}

所以这里没有钻石。无论如何,现在在接口中声明常量是不受欢迎的,大多数时候你最好使用枚举来获得相同的效果。

【讨论】:

  • 如果在接口中声明的常量是一个有用的对象,而不仅仅是一个int 值,那么使用enum 并不是特别方便。我认为真正的错误是使用implements 而不是import static 将常量名称带入本地名称空间。 (不过,对于遗留代码,你只需要使用你继承的东西(可以这么说)。)
  • @TedHopp 同意。对于简单的情况(int 常量),枚举可能是一个更好的主意,对于更复杂的值 import static 是要走的路,但我宁愿在类而不是接口中定义我的常量,它可能会造成混淆这个问题证明了
【解决方案3】:

不,你没有。接口没有任何变量,除了静态最终变量。

如果您实际编写、编译和执行这些接口和类,您就会得到答案。 x 变量不是类成员,因此没有歧义。

这是您可以通过编写代码并让 JDK 告诉您自己轻松回答的问题之一。比在这里问要快。

【讨论】:

  • 完全同意你的看法。但是自从我在这里问了这个问题后,我得到了一些非常好的观点,例如使用 import static,以及在接口中使用枚举而不是常量(正如其他用户所提到的)。你不觉得我有额外的东西要学吗? &你不认为这会帮助我或其他人吗?我不是想在这里浪费别人的时间。如果有人觉得他们可以与我分享一些知识,他们应该……否则,干脆忽略我的问题。像我这样的编程初学者需要像你这样的优秀程序员的帮助,先生。请鼓励我们提问:)
  • 你无法控制我如何回答,就像我无法控制你问什么一样。我不鼓励或不鼓励提问;我只是提出我的意见供您考虑。请把态度留在门口,比曼。谢谢。
【解决方案4】:

Java 阻止多个具体/抽象类继承,但不阻止多个接口继承。使用多接口继承,您继承抽象方法,而不是实现。请参阅这篇带有很好解释和示例的帖子:https://web.archive.org/web/20120724032720/http://www.tech-knowledgy.com/interfaces-sovles-diamond-death/

【讨论】:

  • 非常有用的链接。它解决了我的查询。所以我认为,即使在接口的情况下会出现钻石,也不会有死亡钻石。谢谢:)
  • 是的,因为Java从根本上不是为多重继承而设计的,因为Java中只有一个超级父对象,因此Java通过多重接口继承解决了这个问题
【解决方案5】:

Deadly Diamond of Death 是变量的问题,但虚拟方法的问题更大。如果类Moo1Moo2 都继承自类Foo 并覆盖抽象虚函数Bar,并且如果类Zoo 被允许从Moo1Moo2 继承,而不必添加自己的Bar 覆盖,不清楚ZooBar 应该做什么。接口通过要求实现接口的每个类必须为所有接口成员提供自己的实现,并指定接口的所有成员在直接或间接扩展它的所有接口中将被视为相同,从而避免了这个问题。因此,在上述情况下,如果Foo等是接口而不是类,那么任何实现Zoo的类都需要实现Foo.Bar,这将是Moo1.BarMoo2.Bar的同义词,和Zoo.Bar

【讨论】:

  • 实际上,Deadly Diamond of Death(至少在 C++ 中)的“致命”部分与 -virtual 析构函数有关。在您的示例类结构中,当 Bar 的实例被销毁时,Foo 的(非虚拟)析构函数最终将被调用两次(可能会造成灾难性后果)。 C++ 中的虚拟析构函数有助于解决这个问题;他们不会导致它。
  • @TedHopp:这是一个 C++ 问题。一种根本没有非虚拟析构函数或(如 Java)没有析构函数的语言可以避免这个问题。然而,所引用的歧义更为根本,对于一种语言来说,在不违反 Java 提供的至少一些行为保证的情况下,没有一种干净的方法可以避免这种歧义。
  • 有一个更普遍的(或者,正如你所说的,更基本的)钻石问题,它涉及到歧义。钻石问题通常被称为死亡钻石问题,因为人们会对性和暴力图像做出反应,这些是真正不同(如果相关)的事情。 Deadly Diamond of Death 之所以得名,是因为在对象的正常生命周期中多次调用析构函数(或者,在某些情况下是语言,构造函数)的效果。请注意,如果 Java 具有多重继承,则可能会出现 DDOD 问题,因为 Java 构造函数不是虚拟的。
  • @TedHopp:如果一个类继承了多个基类,除了一个必须声明为父构造函数调用可能是绕过。我想这也可以解决其他一些歧义。析构函数可能是由于 DDoD 而失败的最壮观的东西,但是如果人们不将产生歧义的菱形图案称为“致命”,那么人们将如何称呼它们以将它们与不会产生任何歧义的菱形区分开来?
  • 我称之为钻石问题。如果没有歧义,就没有问题。
【解决方案6】:

Deadly Diamond Of Death Problem.

class A
{
void eat()
{
    print("I am eating Apple")
}
}

B 类和 C 类扩展 A 并覆盖eat() 方法

class B extends A
{
void eat()
{
print("I am eating Banana")
}
}


class C extends A
{
void eat()
{
print("I am eating Grapes")
}
}

现在如果java有多个继承?以下情况会发生什么。

 class D extends B ,C
{
//which eat() method will be inherited here for class D ? a problem  ? ?
}

【讨论】:

  • D 声明不是合法的Java。一个类不能扩展两个基类。
  • 是的,这就是解释“如果”java允许多重继承,它会导致问题。
  • 您的回答与问题有何关联? (这不是关于死亡钻石的定义的问题,而是关于 Java 接口是否在所有情况下都避免它的问题。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-07
  • 1970-01-01
  • 1970-01-01
  • 2013-05-09
  • 2016-08-15
  • 1970-01-01
相关资源
最近更新 更多