Eclipse / javac在使用默认方法冲突编译签名时不同意; 谁是对的?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (38)

以下是一个简单的课程,演示了这个问题:

package com.mimvista.debug;

public class DefaultCollisionTest {
    public static interface Interface1 {
        public String getName();
    }

    public static interface Interface2 {
        public default String getName() { return "Mr. 2"; };
    }

    public static <X extends Interface1&Interface2> String extractName(X target) {
        return target.getName();
    }
}

Eclipse(Neon 2)很高兴地编译这个类,而javac(JDK 1.8.0_121)吐出下面的编译错误:

$ javac src/com/mimvista/debug/DefaultCollisionTest.java
src\com\mimvista\debug\DefaultCollisionTest.java:13: error: class INT#1 inherits abstract and default for getName() from types Interface2 and Interface1
        public static <X extends Interface1&Interface2> String extractName(X target) {
                       ^
  where INT#1 is an intersection type:
    INT#1 extends Object,Interface1,Interface2
1 error

我相信Eclipse在这种情况下是正确的,但我并不完全确定。根据我对“继承抽象和默认”错误的理解,我认为它应该只在编译实现这两个接口的实际声明类时生成。看起来javac可能正在生成一个中间类来处理这个通用签名,并将其错误地进行默认方法碰撞测试?

提问于
用户回答回答于

Eclipse是对的

我没有在Java Bug数据库中发现这个javac错误,因此报告它:JDK-8186643

Stephan Herrmann更好的解释(见他的评论如下):

正确的,只有在交叉点类型不正确并且交叉点为空时才会发生针对交叉点类型的错误报告。但正如这个答案所示,交叉点不是空的,因此应该是合法的。实际上,错误消息class INT#1 inherits ...没有任何意义,因为在那时没有人提到INT#1 ,我们只有两个接口的交集,并且该交集仅用作边界而不是类型。

实现同一方法的多个接口的类可以用两种编译器编译,即使一个接口的方法具有默认实现。<T extends I1 & I2>只要I1I2都没有默认的同名方法实现,就可以引用该类。只有两个接口中的一个具有默认实现javac失败。

如果应该应用实现的模糊性,那么在定义类时应该已经出现错误,而不是在类被引用时<T extends ...>

请参阅以下与<T extends I1 & I2>and <T extends IDefault>协同工作的示例,但使用<T extends I1 & IDefault>and javac时失败:

interface I1 {
    String get();
}

interface I2 {
    String get();
}

interface IDefault {
    default String get() {
        return "default";
    };
}

public class Foo implements I1, I2, IDefault {

    @Override
    public String get() {
        return "foo";
    }

    public static void main(String[] args) {
        System.out.print(getOf(new Foo()));
    }

//  static <T extends I1 & IDefault> String getOf(T t) { // fails with javac
    static <T extends I1 & I2> String getOf(T t) { // OK
        return t.get();
    }

}
用户回答回答于

根据JLS 9.4.1.3, Javac是正确的Interfaces>使用Override-Equivalent签名继承方法

如果一个接口I继承了一个默认方法,其签名被覆盖等价于另一个方法继承I,那么会发生编译时错误。(无论其他方法是抽象还是默认,都是这种情况。)

小字体解释:

当一个抽象和一个匹配签名的默认方法被继承时,我们会产生一个错误。在这种情况下,可以优先考虑其中一个 - 也许我们可以假定默认方法也为抽象方法提供了合理的实现。但这是有风险的,因为除了巧合的名称和签名以外,我们没有理由相信默认方法与抽象方法的合约行为一致 - 当子接口最初开发时,甚至可能不存在默认方法。在这种情况下要求用户主动声明默认实现是合适的(通过重写声明)更安全。 相比之下,类中继承的具体方法的长期行为是它们重写在接口中声明的抽象方法(请参阅第8.4.8节)。关于潜在合同违反的相同论点适用于此,但在这种情况下,类和接口之间存在固有的不平衡。我们宁愿为了保持阶级阶层的独立性,通过简单地优先考虑具体的方法来减少阶级界面的冲突。

还与8.4.8.4比较类>用Override-Equivalent签名继承方法

如果一个C类继承了一个默认方法,它的签名被C继承的另一个方法覆盖 - 等价,那么这是一个编译时错误,除非存在一个在C的超类中声明并由C继承的抽象方法,这两种方法。 严格的默认 - 抽象和默认 - 默认冲突规则的这种例外是在超类中声明抽象方法时产生的:来自超类层次结构的抽象声明的断言基本上胜过了默认方法,使得默认方法的行为如同它是抽象的。但是,类的抽象方法不会覆盖默认方法,因为接口仍然可以改进来自类层次结构的抽象方法的签名。

用更简单的话来说:假设两个接口在逻辑上是无关的,都指定了某种行为契约。因此,假设默认实现Interface2是合同的有效履行并不安全Interface1。抛出一个错误并让开发者把它整理出来更安全。

我没有找到在JLS一个地方,它会准确地解决你的情况,但我认为错误是在上述规范的要点-您声明extractName()应该同时实现的目标Interface1Interface2。但是对于这样一个对象,只有在“存在一个在C的超类中声明的并且被C继承的抽象方法,并且这个方法被这两个方法覆盖等价”时才有效。你的泛型声明没有指定任何关于超类的东西X,所以编译器将它视为“抽象 - 默认”冲突。

扫码关注云+社区