注解处理器似乎破坏了Java的泛型机制。

20

背景

我尝试使用注解处理器来生成特定工厂接口的实现。这些接口如下所示:

public interface ViewFactory<T extends View> {

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

}

并且

public interface PresenterFactory<T extends View> {

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

}

注解处理器正在执行正确的操作,并为每个带有相应注解的匹配类生成工厂。

问题

注解处理器的输出如下:

public final class TestViewImplFactory implements ViewFactory {

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

和相应的另一个类:

public final class TestPresenterImplFactory implements PresenterFactory {

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

然而,TestViewImplFactory无法编译。错误信息如下:

"类'TestViewImplFactory'必须声明为抽象或实现'ViewFactory'中的抽象方法create(S)"

Java指出,以下内容是正确的:

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

考虑到用户想要知道返回哪个视图和需要哪个Presenter,这种方法根本行不通。我原本希望:

  1. 自动生成的文件中要么两个都是错误的
  2. 要么两个都是正确的

因为它们非常相似,我本以为第一种情况是正确的。

我错过了什么吗?


如果我像这样将泛型类型添加到TestViewImplFactory中:

public final class TestViewImplFactory implements ViewFactory<TestView> {

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

问题在于,构造函数参数(类型为TestPresenter)不正确。将S更改为具体的TestPresenter会使该类出现与上述相同的编译错误。
因此,我找到了一个可以编译的“解决方案”。
基本上需要做的是将ViewFactory接口更改为以下内容:
public interface ViewFactory<T extends View, S extends Presenter<T>> {

    T create(S presenter);

}

因此,类定义具有与上述问题中的方法相同的泛型类型。

编译后(这次是具有泛型类型规范),输出如下:

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

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

这段代码可以被编译并且成功运行。

然而,这并没有回答原来的问题。为什么在类型定义中明确指定泛型是正确的,但在方法声明中继承和指定泛型是错误的,并且无法编译?

具体地说:为什么 Java 能够自动继承一个泛型(在 PresenterFactory 中),而不能自动继承其他泛型(在 ViewFactory、方法和类型声明中)?


1
您的注解处理器似乎正在创建原始泛型类型。 - killjoy
@killjoy 或者更可能的是,未能在处理类时反映类型参数,而只是使用已擦除的签名。 - chrylis -cautiouslyoptimistic-
@killjoy 这不应该改变结果。如果这是问题,TestPresenterImplFactory也无法编译。或者我在这里错了吗? - Thorben Kuck
1
如果您正在使用擦除类型,则需要始终使用它们,并仅将擦除类型作为参数并返回相应的擦除。您说用户需要具体(通用)类型,但如果他们只与通用接口交互而不与生成的实现类型交互,则实际上他们不需要这些类型。编译器将插入正确的转换。 - Daniel Pryden
2个回答

16

为什么它不起作用:

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

这个签名会让编译器在调用create()的位置推断出S。你给create()分配的任何内容都将成为S,例如:
FancyPresenter fp = presenterFactory.create();
SomeOtherPresenter sop = presenterFactory.create();

这意味着:
public TestPresenter create(){...}

不是的实现:
<S extends Presenter<T>> S create();

但是这是一种方法重载。接口的方法没有实现。甚至无法使用具体的S提供任何实现。这类似于:

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

这里再次推断了泛型在方法调用时的通用性。因此,实现必须接受Presenter<T>的每个子类型。唯一有效的实现是:

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

但是返回类型取决于参数 presenter。如果 presenter 只提供了一个创建 T 实例的方法,这可能有效。
为什么另一种解决方案可行:
通过类型绑定方法的泛型意味着接口的实现提供了具体类型。因此,对于一个对象,您不需要提供多个不同的绑定。无论在哪里调用 PresenterFactory> 的 create() 方法,返回类型的泛型都绑定到 TestPresenter。因此,每个 PresenterFactory<...> 的子类型都有可能实现。

0

我认为你的问题陈述中应该首先解决的是,我注意到你的注解处理器正在实现原始的ViewFactory类型。我猜想由于类型擦除,既然它是生成的代码,在实践中并没有真正的区别。但如果处理器可以使用参数化类型生成实现,那么至少更容易理解这个问题。

因此,给定一个消息签名<S extends Presenter<T>> T create(S presenter),你可以让它生成:

public class TestViewImplFactory implements ViewFactory<TestView> {
  @Override
  public <S extends Presenter<TestView>> TestView create(S presenter) { ... }
}

更简洁地说:
public class TestViewImplFactory implements ViewFactory<TestView> {
  @Override
  public TestView create(Presenter presenter) { ... }
}

但是,使用这两种方法之一,您无法将参数限制为TestPresenter。 您必须将ViewFactory更改为类似于以下内容:

public interface ViewFactory<T extends View, U extends Presenter<T>>

然后实现ViewFactory<TestView, TestPresenter>。你必须在实现中使用类型参数来实现所需的类型限制。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接