javagenericsjavassistjava-bridge-method

Error: java.lang.AbstractMethodError when invoking a generic method implementing an interface


I am trying to use javassist to programatically create and compile a class (at runtime) that implements an interface.

I get the following Error whenever I invoke an instance of that dynamic class:

java.lang.AbstractMethodError: FooImpl.test()Ljava/lang/Object;

Here's my interface

public class FooBarInterface<T> {
    public T getEntity();
}

Here's a sample Entity

public class FooEntity {

    @Override
    public String toString() {
        return "Hello, Foo!";
    }
}

Here's how I programatically implement the interface

public void test() {
    ClassPool classPool = ClassPool.getDefault();
    CtClass testInterface = classPool.get(FooBarInterface.class.getName());

    CtClass fooImpl = classPool.makeClass("FooImpl");

    fooImpl.addInterface(testInterface);
    CtMethod testMethod = CtNewMethod.make(
        "public com.test.FooEntity getEntity(){" +
            "return new com.test.FooEntity();" +
        "}",
        canImpl
    );

    fooImpl.addMethod(testMethod);

    fooImpl.writeFile();

    TestInterface<FooEntity> test = 
        (TestInterface<FooEntity>) fooImpl.toClass().newInstance();

    System.out.println(test.getEntity());

}

If I changed the return type of the implemented method to Object, then I don't get the Error, like this:

CtMethod testMethod = CtNewMethod.make(
    "public Object getEntity(){" +
        "return new com.test.FooEntity();" +
    "}",
    canImpl
);

Then I successfully get the hello, Foo!. I am OK with changing the return type to Object, but I'd like to understand more why returning with type Foo produces AbstractMethodError.


Solution

  • Inside the JVM, methods with different return types are distinct. After type erasure, FooBarEntity.getEntity() has return type Object. Calls via the interface will look specifically for a method with return type Object, hence why your implementation must return Object.

    Normally, your Java compiler will create bridge methods that forward the result of the concrete method as the erased type, but apparently Javassist doesn't do this for you (I haven't used Javassist so I'm not sure).

    For more on how bridge methods are used to implement type erasure, see the official Java Tutorials topic on bridge methods.