默认方法和扩展其他接口的接口

32

假设有两个接口 Interface1Interface2,其中Interface2 继承自 Interface1

interface Interface1 {

    default void method() {
        System.out.println("1");
    }

    // Other methods
}

interface Interface2 extends Interface1 {

    @Override
    default void method() {
        System.out.println("2");
    }

    // Other methods
}

假设我想创建一个类,实现Interface2接口,但我希望method()使用Interface1中的版本。如果我这样写:

class MyClass implements Interface1, Interface2 {

    public void method() {
        Interface1.super.method();
    }
}

我遇到了编译错误:

默认的超类调用中存在不良类型限定符:冗余接口 Interface1 被 Interface2 继承

可以通过创建第三个接口来解决这个问题:

interface Interface3 extends Interface1 {

    default void method() {
        Interface1.super.method();
    }
}

那么:

class MyClass implements Interface1, Interface2, Interface3 {

    public void method() {
        Interface3.super.method();
    }
}

这段代码可以成功编译,如果我实例化了一个新的MyClass并调用method()方法,那么输出结果如预期的一样是1

我的问题是,既然轻而易举地绕过了只能在接口链中最具体的接口上写InterfaceName.super.method()的限制,为什么还要有这个限制?防止哪些问题出现,不允许我们直接写Interface1.super.method()呢?


3
如果Interface2继承自Interface1,在哪种情况下你希望一个类同时实现这两个接口呢? - Maroun
@MarounMaroun 你说得对,写两个的确没有意义。只是在 IntelliJ 上,如果我只写了 Interface2,我会得到一个不太有用的错误信息。 - Paul Boddington
接口中的方法有实体,我得重新复习基础知识了。我以为它们应该是抽象的。 - Shrikant Havale
2
@ShrikantHavale 这是Java 8的新功能。我几乎希望他们不要允许它,因为它已经引起了一系列问题。 - Paul Boddington
2
@pbabcdefp 如果你正确使用它,就不会有任何问题(这个模糊的陈述适用于许多其他概念)... - assylias
@assylias 接口的整个重点是抽象化,接口中有实现方法的意义何在?它会破坏向后兼容性的设计。 - Stunner
1个回答

18
这一点正是 JLS 中15.12.3所讨论的内容:“编译时步骤 3:已选方法是否适当?”

如果形式为 TypeName . super . [TypeArguments] Identifier,则:

  • 【...】
  • 如果 TypeName 表示一个接口,则让 T 为立即包含方法调用的类型声明。如果存在一个从 T 的直接超类或直接超接口中覆盖(§9.4.1)该编译时声明的方法,且该方法与编译时声明不同,则会发生编译时错误。

JLS 进一步解释了此规则的原因:

如果一个超级接口覆盖了在祖先接口中声明的方法,这条规则会防止子接口通过简单地将祖先添加到其直接超级接口列表中来“跳过”覆盖。访问祖先的功能的适当方式是通过直接超级接口,并且仅当该接口选择公开所需行为时才这样做。
因此,它存在的主要目的是阻止您尝试做的事情。
但是JLS似乎也承认了您的解决方法:
(或者,开发人员可以自由定义自己的附加超级接口,以使用超级方法调用公开所需的行为。)

这真的很有趣。他们对这个变通方法感到满意(但在我看来似乎是一种黑客攻击),但他们禁止原始方法的唯一原因是他们认为它不“适当”。 - Paul Boddington
1
是的,我也觉得很奇怪。如果例如javadoc自动检测子类选择使用哪个实现并生成相应的文档,那么我会理解。但我怀疑这不是情况,因为据我所知,javadoc不关心方法的主体。 - JB Nizet
2
我认为“他们对此没意见”是对这条评论的误读(大多数情况下,听到自己想听的)。一般来说,规则存在是有很好的理由的(甚至在这里也给出了原因);当编译器说“不要那样做”时,你的第一个想法不应该是“让我拆掉保护罩,向保姆国家证明谁最聪明”。在这种情况下,无法防止用户在所有情况下都做傻事,但仍不建议这样做,这并不意味着我们应该放弃在可以时警告用户。 - Brian Goetz
@BrianGoetz 谢谢您的评论。明确地说,我同意关于干涉过多的想法。个人感觉,让我印象深刻的是文章中居然提到了这点。 (如果规则足够重要而值得加上注释,为什么还要立刻指出如何打破它呢?)在大多数情况下,只有算法会被描述。 - Radiodef
2
@Radiodef 公正的评论。现实情况是:多重继承比单一继承更加复杂,即使通过禁止多重继承状态和选择巧妙的冲突解决规则来避免90%的复杂性。用户已经有20年的“这就是继承/覆盖意味着什么”的经验,受到单一继承的制约,当他们尝试将其应用于多重继承时,他们的直觉经常会失败。我们可以通过禁止真正有问题的部分使它变得更容易,但“更容易”和“容易”并不相同。 - Brian Goetz
2
@BrianGoetz 我明白了。所以无论这个特性是否有用,我们都有机会在我们熟悉的概念之外(“意大利面类型”)自己给自己惹麻烦,而且很难禁止这种可能性。(经过思考后,似乎不允许覆盖绕过技巧的冲突解决方案往往比有用的更为严格。)再次感谢您的时间。 - Radiodef

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