使用在运行时确定的类调用泛型方法

时间:2018-03-09 19:34:11

标签: java generics java-8 jersey-2.0 hk2

我正在尝试使用jersey 2 / HK2自动将工厂类绑定到某个注释。因此,我在运行时从通用接口获取提供的类型,然后尝试将工厂绑定到此类型。将工厂绑定到类的方法如下所示:

protected void bindResourceFactory(Class<? extends Factory<?>> factory) {
  Class<?> providedClass = getProvidedClass(factory);
  bindFactory(factory).to(providedClass).in(Singleton.class);
}

HK2提供的bindFactoy方法定义如下:

public <T> ServiceBindingBuilder<T> bindFactory(Class<? extends Factory<T>> factoryType) {
    return resetBuilder(AbstractBindingBuilder.<T>createFactoryBinder(factoryType, null));
}

当我用eclipse构建所有东西时,这似乎工作得很好。但是,当我使用maven构建项目时,我收到以下构建错误:

[ERROR] /Users/jan/Documents/Workspace/jersey-test/bind/ResourceFactoryBinder.java:[32,5] no suitable method found for bindFactory(java.lang.Class<capture#1 of ? extends org.glassfish.hk2.api.Factory<?>>)
[ERROR]     method org.glassfish.hk2.utilities.binding.AbstractBinder.<T>bindFactory(java.lang.Class<? extends org.glassfish.hk2.api.Factory<T>>,java.lang.Class<? extends java.lang.annotation.Annotation>) is not applicable
[ERROR]       (cannot infer type-variable(s) T
[ERROR]         (actual and formal argument lists differ in length))
[ERROR]     method org.glassfish.hk2.utilities.binding.AbstractBinder.<T>bindFactory(java.lang.Class<? extends org.glassfish.hk2.api.Factory<T>>) is not applicable
[ERROR]       (cannot infer type-variable(s) T
[ERROR]         (argument mismatch; java.lang.Class<capture#1 of ? extends org.glassfish.hk2.api.Factory<?>> cannot be converted to java.lang.Class<? extends org.glassfish.hk2.api.Factory<T>>))
[ERROR]     method org.glassfish.hk2.utilities.binding.AbstractBinder.<T>bindFactory(org.glassfish.hk2.api.Factory<T>) is not applicable
[ERROR]       (cannot infer type-variable(s) T
[ERROR]         (argument mismatch; java.lang.Class<capture#1 of ? extends org.glassfish.hk2.api.Factory<?>> cannot be converted to org.glassfish.hk2.api.Factory<T>))

两种情况下的Java版本都是1.8.0_152。

原因可能是我使用的参数类型为Class<? extends Factory<?>>,而bindFactory期望Class<? extends Factory<T>>。有人知道,为什么这可能是用eclipse而不是用maven构建的?除了通过反射调用bindFactory之外,还有什么方法可以使这项工作成为可能吗?

2 个答案:

答案 0 :(得分:2)

发生此错误的原因是编译器未捕获转换Class<? extends Factory<?>>中的“内部”通配符(Factory<?>中的那个)。 (就规范而言,"capture conversion is not applied recursively"。)

更容易解释为什么这应该以不同的方式发生(但与所涉及的类型类似)示例。假设我们有List任何类型的List

List<List<?>> lists = ...;

现在假设我们有一些处理列表列表的方法,但假设列表都具有相同的类型:

<T> void process(List<List<T>> lists) {
    // and at this point we should note that List<T>
    // allows us to add elements to the lists, so we
    // could do something like this:

    if (!lists.isEmpty()) {
        List<T> list0 = lists.get(0);

        for (int i = 1; i < lists.size(); ++i)
            list0.addAll(lists.get(i));
    }
}

所以问题是:我们是否应该将List<List<?>>传递给process方法?好吧,可能是我们用以下方式构建了列表列表:

List<Double> doubles = new ArrayList<>();
Collections.addAll(doubles, 0.0, 1.0, 2.0);

List<String> strings = new ArrayList<>();
Collections.addAll(strings, "X", "Y", "Z");

List<List<?>> lists = new ArrayList<>();
Collections.addAll(lists, strings, doubles);

在这种情况下,我们不应该将List<List<?>>传递给采用List<List<T>>的流程方法。编译器实际实现的方式是它不会将“内部”通配符捕获到某个类型变量T

问题中的代码由于非常相似的原因而无法编译。由于Class上的类型参数主要与构造函数相关的方法(尤其是newInstance方法)相关,因此我们可以使用Supplier显示更相似的示例:

static void example(Supplier<? extends Factory<?>> s) {
    capture(s);
}
static <T> void capture(Supplier<? extends Factory<T>> s) {
    Factory<T> a = s.get();
    Factory<T> b = s.get();
    // remember, a and b are supposed to have the same type
    T obj = a.provide();
    b.dispose(obj);
}

问题在于,由于我们的供应商最初可能是Supplier<Factory<?>>,因此没有理由不能从一次调用返回Factory<String>而从另一次调用返回Factory<Double>。因此,我们无法将Supplier<Factory<?>>捕获到Supplier<Factory<T>>Class.newInstance将始终返回完全相同类型的对象,但编译器不知道。

我认为Eclipse的编译器在这种情况下编译代码可能是错误的。

如果你想强迫这个编译,你可以使用未经检查的强制转换作为评论中的用户建议,但我不太了解所涉及的类来说明是否结果这实际上是可证明的正确。上面两个代码示例显示了做这样的事情实际上可能会出现可怕的错误(原则上),但Class有时是一个特例。

修复它的一种方法更合适的方法是在bindResourceFactory上声明一个类型变量,所以它也需要一个Class<? extends Factory<T>>,但我不知道这是否真的适合你的方式'重新调用方法。

答案 1 :(得分:2)

这是因为eclipse使用它自己的名为ECJ的编译器,而maven使用javac编译器。有时在ECJ中编译的代码不能在javac中编译,反之亦然。

在这种特殊情况下,eclipse编译器能够推断出泛型类型 T ,但javac不是。因此,您需要明确告诉类型 T ,这是未知的,因为收到的类型是Class<? extends Factory<?>>,这意味着您应该使用Object,如下所示。

this.<Object>bindFactory((Class<? extends Factory<Object>>) factory);

在这种情况下,工厂需要进行转换,并且this.<Object>可以省略,因为编译器已经推断出Object。

最后,您可以取消投射警告,并且最好取消选中&#39;尽可能少的代码。

@SuppressWarnings({ "unchecked" })
Class<? extends Factory<Object>> objFactory = (Class<? extends Factory<Object>>) factory;
bindFactory(objFactory).to(providedClass).in(Singleton.class);

要考虑的一个重要事项是方法getProvidedClass(...)应该正确返回类

在此<T> void bindResourceFactory(Class<? extends Factory<T>>)方法中使用泛型会再次将您带到同一个地方,因为您无法使用扩展Factory<?>的类调用该方法通配符(Class<? extends Factory<?>>)。