Annotation Processor appears to break Java generics

Thorben Kuck :

Background

I was trying to use Annotation Processors, to generate implementations of specific Factory interfaces. Those interfaces look the following:

public interface ViewFactory<T extends View> {

    <S extends Presenter<T>> T create(S presenter);

}

and

public interface PresenterFactory<T extends View> {

    <S extends Presenter<T>> S create();

}

The Annotation Processor is doing the correct thing and generates a factory for each matching class, that is annotated with an corresponding annotation.

The Problem

The output of the Annotation Processor is the following:

public final class TestViewImplFactory implements ViewFactory {

    public final TestView create(TestPresenter presenter) {
        return new TestViewImpl(presenter);
    }
}

and the corresponding other class:

public final class TestPresenterImplFactory implements PresenterFactory {

    public final TestPresenter create() {
        return new TestPresenterImpl();
    }
}

The TestViewImplFactory however cannot be compiled. The error message is:

"Class 'TestViewImplFactory' must be declared abstract or implement abstract method create(S) in 'ViewFactory'"

Java says, the following is correct:

@Override
public View create(Presenter presenter) {
    return new TestViewImpl(presenter);
}

which would not work at all, considering that the user wants to know, which View will be returned and which Presenter is required. I would have expected that:

  1. either both of the autogenerated files are wrong
  2. or both are correct

because they both are really similar. I expected the first to be true.

What am I missing here?


If I add the Generic type to the TestViewImplFactory like this:

public final class TestViewImplFactory implements ViewFactory<TestView> {

    @Override
    public <S extends Presenter<TestView>> TestView create(S presenter) {
        return new TestViewImpl(presenter);
    }
}

The problem arises, that the constructor Parameter (which is of the Type TestPresenter) is incorrect. Changing the S to a concrete TestPresenter will, again, make the class not compilable for the same reason as above.


So, I stumbled across an "solution" that can be compiled.

What basically has to be done, is to change the ViewFactory interface to the following:

public interface ViewFactory<T extends View, S extends Presenter<T>> {

    T create(S presenter);

}

So the class definition has the same Generic type, as the method in the Question above.

After compilation (this time with generic type specification), the output looks like this:

public final class TestViewImplFactory implements ViewFactory<TestView, TestPresenter> {
    public TestViewImplFactory() {
    }

    public final TestView create(TestPresenter presenter) {
        return new TestViewImpl(presenter);
    }
}

This can be compiled and runs successfully.

This however does not answer the original question. Why is the generic explicitly stated in the type definition correct, but inherited and specified in the method declaration wrong and not compilable?

To be concrete: Why can Java inherit one Generic automatically (within the PresenterFactory) and the other ones not (within the ViewFactory, at the method and at the type declaration)?

Trinova :

Why it is not working:

public interface PresenterFactory<T extends View> {
    <S extends Presenter<T>> S create();
}

This signature causes the compiler to infer S at the location where create() is called. S will be what ever you assign create() to as in:

FancyPresenter fp = presenterFactory.create();
SomeOtherPresenter sop = presenterFactory.create();

This implies that:

public TestPresenter create(){...}

is not an implementation of:

<S extends Presenter<T>> S create();

but a method override. There is no implementation of the interface' method. It's not even possible to provide any implementation with a concrete S. It's similar with:

public interface ViewFactory<T extends View> {
    <S extends Presenter<T>> T create(S presenter);
}

Here the generic is again inferred on method invocation. So an implementation must accept every subtype of Presenter<T>. The only valid implementation for this is:

public interface ViewFactory<T extends View> {
    T create(Presenter<T> presenter);
}

But the return type is dependent on the parameter presenter. This might work if presenter provides you with a method to create an instance of T only.

Why does the other solution work:

Binding the method's generic via the type means that an implementation of the interface provides the concrete type. So for one object you don't need to provide multiple different bindings. No matter where you call the create() method of PresenterFactory<TestView, TestPresenter<TestView>> the return type's generic is bound to TestPresenter<TestView>. So there is a possible implementation for each subtype of PresenterFactory<...>.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=35402&siteId=1