Home > Mobile >  Kotlin SAM/Functional Interface throws AbstractMethodError
Kotlin SAM/Functional Interface throws AbstractMethodError

Time:06-25

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.

  • Related