类型变量的无关默认值继承错误:为什么?

时间:2016-01-06 22:41:15

标签: java java-8 type-variables

免责声明:这种情况(虽然错误听起来相同):class inherits unrelated defaults for spliterator() from types java.util.Set and java.util.List

这就是原因:

考虑两个接口(包#34; a")

interface I1 {
    default void x() {}
}

interface I2 {
    default void x() {}
}

我很清楚为什么我们不能宣布这样的课程:

abstract class Bad12 implements I1, I2 {
}

(!)但参考类型变量我无法理解这个限制:

class A<T extends I1&I2> {
    List<T> makeList() {
        return new ArrayList<>();
    }
}

错误:class java.lang.Object&a.I1&a.I2 inherits unrelated defaults for x() from types a.I1 and a.I2

为什么我不能定义这样的类型变量?在这种情况下,为什么java关心不相关的默认值?什么类型的变量可以&#34;打破&#34;?

更新:只是为了澄清。我可以创建几个表格类:

class A1 implements I1, I2 {
    public void x() { };
}

class A2 implements I1, I2 {
    public void x() { };
}

甚至

abstract class A0 implements I1, I2 {
    @Override
    public abstract void x();
}

等等。为什么我不能为这类类声明特殊类型的变量?

UPD-2: BTW我在JLS中没有找到任何明显的限制。通过引用JLS确认您的答案会很高兴。

UPD-3:一些用户告诉我这个代码在Eclipse中编译得很好。我无法检查,但我查看javac并收到此错误:

 error: class INT#1 inherits unrelated defaults for x() from types I1 and I2
class A<T extends I1&I2> {
        ^
  where INT#1 is an intersection type:
    INT#1 extends Object,I1,I2
1 error

3 个答案:

答案 0 :(得分:7)

这只是一个错误。事实证明,bug在规范中开始,然后溢出到实现中。规范错误在这里:https://bugs.openjdk.java.net/browse/JDK-7120669

约束完全有效;显然存在类型T延伸I1和I2。问题是我们如何验证这些类型的良好结构。

答案 1 :(得分:0)

“交集类型”(由几个接口的 union 指定的类型)的机制起初可能看起来很奇怪,特别是当与泛型和类型擦除的功能配对时。如果引入几个类型边界,其接口不相互扩展,则只使用第一个作为擦除。如果你有这样的代码:

public static <T extends Comparable<T> & Iterable<String>>
int f(T t1, T t2) {
    int res = t1.compareTo(t2);
    if (res!=0) return res;
    Iterator<String> s1 = t1.iterator(), s2 = t2.iterator();
    // compare the sequences, etc
}

然后生成的字节码将无法在T的擦除中使用Iterable .T的实际擦除将只是可比较的,并且生成的字节码将包含适当的转换为Iterable(发送)除了通常的checkcast操作码之外,还有一个invokeinterface到Iterable,导致代码在概念上等同于以下内容,唯一的区别是编译器还会检查Iterable<String>边界:

public static int f(Comparable t1, Comparable t2) {
    int res = t1.compareTo(t2);
    if (res!=0) return res;
    Iterator s1 = ((Iterable)t1).iterator(), s2 = ((Iterable)t2).iterator();
    // compare the sequences, etc
}

在接口中使用override等效方法的示例中的问题是,即使存在符合您请求的类型边界的有效类型(如注释中所述),编译器也不能以任何有意义的方式使用该事实由于存在至少一种default方法。

考虑通过使用自己的方法覆盖默认值来实现I1和I2的示例类X. 如果您的类型绑定要求extends X而不是extends I1&I2,编译器会接受它,将T擦除到X并在每次使用f时插入invokevirtual X.f()指令。但是,对于类型绑定,编译器会将T擦除为I1。由于在第二种情况下“连接类型”X不是真实的,因此在每次使用t.f()时,编译器需要插入 invokeinterface I1.f()invokeinterface I2.f()。由于编译器不可能插入对“Xf()”的调用,即使它在逻辑上知道 可能使类型X实现I1&amp; I2,并且任何此类X 必须声明该功能,它无法在两个接口之间做出决定而必须纾困。

在没有任何default方法的特定情况下,编译器可以简单地调用任一函数,因为在这种情况下它知道任何invokeinterface调用将明确地被实现在任何有效X中的单个函数中。但是,当默认方法进入图片时,当考虑部分编译时,不再假设此解决方案产生有效代码。请考虑以下三个文件:

// A.java
public class A {
    public static interface I1 {
        void f();
        // default int getI() { return 1; }
    }
    public static interface I2 {
        void g();
        // default int getI() { return 2; }
    }
}

// B.java
public class B implements A.I1, A.I2 {
    public void f() { System.out.println("in B.f"); }
    public void g() { System.out.println("in B.g"); }
}

// C.java
public class C {
    public static <T extends A.I1 & A.I2> void test(T var) {
        var.f();
        var.g();
        // System.out.println(var.getI());
    }

    public static void main(String[] args) {
        test(new B());
    }
}
  • 首先编译A.java,其代码如图所示,生成接口A.I1和A.I2的“v1.0”
  • 接下来编译B.java,生成(此时)实现接口的有效类
  • C.java现在可以再次编译,如图所示,编译器接受它。它会打印您期望的内容。
  • A.java中的默认方法是未注释的,文件被重新编译(生成接口的“v.1.1”),但是B.java 重建它。这类似于升级JRE核心库,而不是您正在使用的其他库实现了一些JRE接口。
  • 最后,我们尝试重建C.java,因为我们将使用最新JRE的新功能。无论我们是否取消对getI的调用,编译器都会拒绝交集类型声明,并提出相同的错误。

如果编译器在第二次构建C.class时接受交集类型(A.I1 & A.I2)是有效的,那么像B这样的现有类会冒一个{{ 1}}在运行时,因为无论是在B还是在Object中都无法解析对任何地方的getI,并且默认方法搜索会找到两种不同的默认方法。编译器通过禁止违规类型绑定来保护您免受可能的运行时错误的影响。

但请注意,如果绑定被IncompatibleClassChangeError替换,则仍可能发生错误。但是,我认为这最后一点是编译器错误,因为编译器现在可以看到T extends B的默认方法使用覆盖等效签名,但不会覆盖它们,因此确保冲突。

主要修改:删除了第一个(可能令人困惑的)示例,并添加了一个解释+示例,说明为什么不允许使用默认值的特定情况。

答案 2 :(得分:-2)

您的问题是:为什么我不能为这类类声明特殊类型的变量?

答案是:因为在您的课程组<T extends I1&I2> void x()中有两个默认实现。类型变量的任何具体实现都必须覆盖这些默认值。

您的A1和A2具有void x()的不同(但覆盖等效)定义。

您的A0是void x()的重写定义,它取代了默认值。

class A<T extends I1&I2> {
  List<T> makeList() {
      return new ArrayList<>();
  }

  public static void main(String[] args) {
    // You can't create an EE to put into A<> which has a default void x()
    new A<EE>();
  }
}

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

JLS 4.4 类型变量不能同时是两个接口类型的子类型,这两个接口类型是同一通用接口的不同参数化,或者发生编译时错误。

JLS 4.9 每个交叉路口类型T1&amp; ......&amp;为了识别交叉点类型的成员,引入一个名义类或接口,如下所示:

• 对于每个Ti(1≤i≤n),令Ci是最特定的类或阵列类型,使得Ti <:Ci。然后必须有一些Ck,使得任何i(1≤i≤n)的Ck <:Ci,或者发生编译时错误。