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

免责声明 :这不是关于这种情况(虽然错误听起来一样): class从类型java.util.Set和java.util.Listinheritancespliterator()的无关默认值

这就是为什么:

考虑两个接口(在包“ a ”中)

 interface I1 { default void x() {} } interface I2 { default void x() {} } 

我绝对清楚为什么我们不能宣布这样的类:

 abstract class Bad12 implements I1, I2 { } 

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

 class A { List 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关心不相关的默认值? 什么类型的变量可以“打破”?

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

 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 { ^ where INT#1 is an intersection type: INT#1 extends Object,I1,I2 1 error 

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

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

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

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

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

 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 } 

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

考虑通过使用自己的方法覆盖默认值来实现I1和I2的示例类X. 如果你的类型绑定要求extends X而不是extends I1&I2 ,编译器会接受它,将T擦除到X并在每次使用f时插入invokevirtual Xf()指令。 但是,对于类型绑定,编译器会将T擦除为I1。 由于“连接类型”X在第二种情况下不是真实的,因此在每次使用tf()时,编译器都需要插入invokeinterface I1.f()invokeinterface I2.f() 。 由于编译器不可能插入对“Xf()”的调用,即使它在逻辑上知道类型X可能实现I1和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 Bf"); } public void g() { System.out.println("in Bg"); } } // C.java public class C { public static  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的新function。 无论我们是否取消对getI的调用,编译器都会拒绝交集类型声明,并提出相同的错误。

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

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

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

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

答案是:因为在你的类组中, void x()两个默认实现。 类型变量的任何具体实现都必须覆盖这些默认值。

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

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

 class A { List 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(); } } 

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

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

JLS 4.9每个交集类型T1&…&Tninduces一个名义类或接口,用于识别交集类型的成员,如下所示:

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