3

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.

Jeffrey Bosboom
  • 13,313
  • 16
  • 79
  • 92
jespeno
  • 159
  • 1
  • 11

3 Answers3

2

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.

Jeffrey Bosboom
  • 13,313
  • 16
  • 79
  • 92
1

I was having same error. I had a base class in which I declared a new abstract method. I implemented that method on the other classes that were consuming it. Now on debugging I was getting Abstract method error as soon as I was hitting implementation of the method.

Solution-: I figured that base class been consumed by other artifacts too and I didn't overrode newly created abstract method in those artifacts. Since I never build them as I was not changing them, JVM never throws compile time error but on run time exception occurs. On implementing method in other artifacts I was able to get rid of the exception.Basically in my case all child classes didn't have implementation of base class's abstract method.

Mr. Wonderful
  • 189
  • 4
  • 13
  • In my case, I was not aware that during type erasure, when the JVM compiles, it automatically adds a bridge method to the subtype (of a generic class/interface) to preserve polymorphism. Since I was using javassists to compile the class at runtime, I did not get the benefit of getting the bridge method automatically - or at least, I don't know how to tell javassist to provide bridge methods. – jespeno Apr 12 '15 at 04:35
0

When you have a parameterized parameter or return type, the Java compiler compiles it as though it was Object and synthesizes a bridge method with the parameterized signature that calls the other one. Or possibly the other way around. You've only synthesized one of them, not both.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • Thank you for answerin!. I will try to check javassist docs whether they also support synthesizing an implementation with a specific type. – jespeno May 13 '14 at 06:24