为什么SamTest2
接口在访问other
属性时会抛出AbstractMethodError
?接口中提供了默认实现,因此我假设在main
方法中创建的匿名实例可以正常工作。这是一个错误的假设吗?
fun interface SamTest1 {
fun method1(value: String): Boolean
fun other(): String = "test"
}
fun interface SamTest2 {
fun method1(value: String): Boolean
val other: String
get() = "test"
}
fun main() {
val sam1 = SamTest1 { true }
println(sam1.other()) // test
val sam2 = SamTest2 { true }
println(sam2.other) // AbstractMethodError
}
我正在查看为代码生成的JVM字节码。SamTest1
和SamTest2
看起来几乎相同(除了一些内部事物的名称),这是有意义的,因为在JVM端,val
与相同类型的0参数fun
没有什么不同。
public interface SamTest1 {
public boolean method1(@NotNull String var1);
public String other();
public static final class DefaultImpls {
public static String other(SamTest1 this_) {
return "test";
}
}
}
public interface SamTest2 {
public boolean method1(@NotNull String var1);
public String getOther();
public static final class DefaultImpls {
public static String getOther(SamTest1 this_) {
return "test";
}
}
}
当我们看到你的main
时,令人兴奋的部分就来了。清理一些工件,它看起来像
static final class sam1$1 implements SamTest1 {
public static final sam1$1 INSTANCE = new sam1$1();
public final boolean method1(String it) {
return true;
}
public String other() {
return SamTest1.DefaultImpls.other(this);
}
}
public static final void main() {
SamTest1 sam12 = sam1$1.INSTANCE;
System.out.println(sam12.other());
SamTest2 sam2 = samKt::main$lambda$0;
System.out.println(sam2.getOther());
}
private static final boolean main$lambda$0(String it) {
return true;
}
因此,据我所知,Kotlin认识到SamTest1
有两个方法,因此(就Java而言)不是SAM接口,因此它创建了一个新类来表示您的匿名实例。方法的默认值在匿名类中传递(就像在Kotlin中应该发生的那样),一切都很好。
但是看起来Kotlin认为SamTest2
与Java的SAM接口机制兼容,并简单地传递给它一个方法samKt::main$lambda$0
,希望JVM端的SAM机制能够综合地构造对象。如果实际上有一个抽象方法,这个将工作得很好,但是在JVM端有两个:method1
和getOther
,因此后者在调用时得到AbstractMethodError
。
我很想证明自己是错的,但我认为这是Kotlin的bug。它应该为每个类生成合成类。我在错误跟踪器上找不到任何类似的问题,但我认为编译器的行为在这里是不正确的。
注意:我使用CFR将JVM字节码反编译回Java。我删除了很多空检查、元数据注释和类型强制转换,以使本文更简洁。