“交集类型”(由多个接口的并集指定的类型)的机制起初可能看起来很奇怪,特别是当与泛型和类型擦除的功能配对时。如果引入多个类型边界,这些边界的接口不相互扩展,则只有第一个类型边界用作擦除。如果您有这样的代码:
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的实际擦除将只是可比的,并且生成的字节码将包含适当的Cast到Iterable(除了通常的操作码之外,还向Iterable发出a),导致代码在概念上等效于以下内容,唯一的区别是编译器还检查边界:checkcast
invokeinterface
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
}
在接口中使用重写等效方法的示例中,问题在于,即使可能存在符合所请求的类型边界的有效类型(如注释中所述),由于存在至少一个方法,编译器也无法以任何有意义的方式使用该事实。default
考虑示例类 X,它通过使用自己的方法重写默认值来实现 I1 和 I2。如果你的类型绑定要求而不是编译器会接受它,则将 T 擦除到 X 并在每次使用 f 时插入指令。但是,绑定类型后,编译器会将 T 擦除到 I1。由于“连接类型”X在第二种情况下不是真实的,因此在t.f()的每次使用上,编译器都需要插入或。由于编译器不可能插入对“X.f()”的调用,即使它在逻辑上知道X类型可以实现I1&I2,并且任何此类X必须声明该函数,它也无法在两个接口之间做出决定并且必须拯救。extends X
extends I1&I2
invokevirtual X.f()
invokeinterface I1.f()
invokeinterface I2.f()
在没有任何方法的特定情况下,编译器可以简单地调用任一函数,因为在这种情况下,它知道任何一个调用都将明确地在任何有效X的单个函数中实现。但是,当默认方法进入画面时,当考虑部分编译时,此解决方案不能再假定生成有效代码。请考虑以下三个文件:default
invokeinterface
// 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());
}
}
- 首先编译.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时接受交集类型为有效,则会承担现有类(如 B)在运行时引发 a 的风险,因为对 getI anywhere 的调用在 B 或 Object 中都不会解析,并且默认方法搜索将找到两个不同的默认方法。编译器通过禁止有问题的类型绑定来保护您免受可能的运行时错误的影响。(A.I1 & A.I2)
IncompatibleClassChangeError
但请注意,如果将绑定替换为 ,则仍可能发生错误。但是,我认为最后一点是编译器错误,因为编译器现在可以看到其默认方法具有重写等效签名,但不会重写它们,从而确保冲突。T extends B
B implements A.I1, A.I2
主要编辑:删除了第一个(可能令人困惑的)示例,并添加了一个解释+示例,说明为什么不允许使用默认值的特定情况。