Why does the SamTest2
interface throw an AbstractMethodError
when accessing the other
property? A default implementation is provided in the interface so I assumed the anonymous instance created in the main
method would work properly. Is this an incorrect assumption?
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
}
CodePudding user response:
I'm looking at the generated JVM bytecode for your code. SamTest1
and SamTest2
look almost identical (save for some names of internal things), which makes sense because, on the JVM side, that val
is no different from a 0-argument fun
of the same type.
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";
}
}
}
The exciting part comes when we look at your main
. Cleaning up a few of the artifacts, it looks like
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;
}
So, as far as I can tell, Kotlin recognizes that SamTest1
has two methods and hence is (as far as Java is concerned) not a SAM interface, so it creates a new class to represent your anonymous instance. The default value for the method gets passed through in the anonymous class (as is supposed to happen in Kotlin), and everything is fine.
But it looks like Kotlin thinks SamTest2
is compatible with Java's SAM interface mechanism and simply passes it a method samKt::main$lambda$0
, hoping the SAM mechanism on the JVM side will construct the object synthetically. This would work fine if there was actually one abstract method, but there's two on the JVM side: method1
and getOther
, so the latter gets AbstractMethodError
when called.
I'd love to be proven wrong on this, but I think this is a Kotlin bug. It should be generating synthetic classes for each. I can't find any similar issues on the bug tracker, but I think the behavior of the compiler is incorrect here.
Note: I used CFR to decompile the JVM bytecode back into Java. I removed a lot of null checks, metadata annotations, and type casts to make things concise for this post.