Eclipse/javac不同意使用默认方法碰撞编译签名; 谁对吗?

Bon*_*ord 16 java eclipse generics javac java-8

这是一个演示该问题的简单类:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

我相信Eclipse在这种情况下是正确的,但我不完全确定.基于我对"继承抽象和默认"错误的理解,我认为只应在编译实现这两个接口的实际声明的类时生成它.似乎javac可能会在引擎盖下生成一个中间类来处理该通用签名并错误地将其置于默认方法冲突测试中?

Ada*_*lik 6

根据JLS 9.4.1.3, Javac是正确的.接口>使用覆盖等效签名继承方法:

如果接口I继承了一个默认方法,该方法的签名与另一个继承的方法等效,I则会发生编译时错误.(这是另一种方法是抽象的还是默认的.)

小字说明:

[...]当继承具有匹配签名的抽象和默认方法时,我们会产生错误.在这种情况下,可以优先考虑一个或另一个 - 也许我们假设默认方法也为抽象方法提供了合理的实现.但这是有风险的,因为除了巧合的名称和签名之外,我们没有理由相信默认方法与抽象方法的契约一致 - 默认方法在最初开发子接口时可能不存在.在这种情况下,要求用户主动声明默认实现是合适的(通过覆盖声明)更安全.

相比之下,类中继承的具体方法的长期行为是它们覆盖在接口中声明的抽象方法(参见§8.4.8).关于潜在合同违规的相同论点适用于此,但在这种情况下,类和接口之间存在固有的不平衡.为了保持类层次结构的独立性,我们更喜欢通过简单地优先使用具体方法来最小化类接口冲突.

也与8.4.8.4进行比较.类>使用覆盖等效签名继承方法:

如果类C继承了一个默认方法,它的签名与C继承的另一个方法等效,那么这是一个编译时错误,除非存在一个在C的超类中声明并由C继承的抽象方法,它是覆盖等效的这两种方法.

当在超类中声明抽象方法时,会对严格的default-abstract和default-default冲突规则进行此异常:来自超类层次结构的abstract-ness断言基本上胜过默认方法,使得默认方法就像使用默认方法一样它是抽象的.但是,类中的抽象方法不会覆盖默认方法,因为仍允许接口优化来自类层次结构的抽象方法的签名.

甚至更平常的话:假设两个接口在逻辑上是不相关的,并且都指定了某种行为契约.因此,假设默认实施Interface2是有效履行合同是不安全的Interface1.抛出错误并让开发人员对其进行排序更安全.

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

  • 由于第一行,我认为规范的这一部分实际上并不适用:"如果接口'我'继承了默认方法......"在我的示例类中,没有'Interface I'(即有没有声明的类或接口实际上继承了"getName"方法)所以这个规则不应该发挥作用.(规则WOULD必须应用于实际满足我的'extractName'方法的参数范围的任何类,但示例案例不包含此类.) (3认同)

how*_*ger 6

Eclipse是对的.

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

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

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

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

如果应用实施应该存在歧义,则在定义类时应该已经出现错误,而不是在引用类时<T extends ...>(参见JLS 4.9.交叉类型).

请参阅以下示例,该示例适用于<T extends I1 & I2><T extends IDefault>,但是使用<T extends I1 & IDefault>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();
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 是的,仅当交叉点类型格式不正确且因此交叉点为空时,才应报告交叉点类型的错误.但正如这个答案所示,交叉点不是空的,因此应该是合法的.实际上,错误消息`class INT#1继承...`没有意义,因为那时没人提到**类**INT#1,我们只有两个**接口**的交集,那个交集仅用作绑定,而不是类型. (2认同)