【问题标题】:Call generic method with class determined at runtime使用在运行时确定的类调用泛型方法
【发布时间】:2018-03-09 19:34:11
【问题描述】:

我正在尝试使用 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&lt;? extends Factory&lt;?&gt;&gt; 类型,而bindFactory 需要Class&lt;? extends Factory&lt;T&gt;&gt;。有人知道,为什么这可能是用 eclipse 而不是用 maven 构建的?除了通过反射调用bindFactory 之外,还有什么方法可以使这项工作正常进行吗?

【问题讨论】:

  • 嗯,差异可能是因为 eclipse 使用它自己的编译器 (ECJ) 而 maven 使用 java 的。如果您在 bindFactory 中指定泛型类型,它会起作用吗?例如 bindFactory(...).
  • 很高兴知道!不,在 eclipse 或 maven 中无法编译:The parameterized method &lt;Object&gt;bindFactory(Class&lt;? extends Factory&lt;Object&gt;&gt;) of type AbstractBinder is not applicable for the arguments (Class&lt;capture#8-of ? extends Factory&lt;?&gt;&gt;)
  • 嗯,有道理,尝试铸造工厂this.&lt;Object&gt;bindFactory((Class&lt;? extends Factory&lt;Object&gt;&gt;) factory)。抱歉,我现在手头没有日食。
  • 是的,这行得通bindFactory((Class&lt;Factory&lt;Object&gt;&gt;) factory)
  • 太好了,我会发布更详细的答案。

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


【解决方案1】:

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

在这种特殊情况下,eclipse 编译器能够推断出泛型类型 T,但 javac 不能。所以你需要明确告诉类型T,这是未知的,因为接收到的类型是Class&lt;? extends Factory&lt;?&gt;&gt;,这意味着你应该像下面这样使用Object

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

在这种情况下,需要强制转换工厂,并且可以省略 this.&lt;Object&gt;,因为编译器已经推断出 Object。

最后你可以取消强制转换警告,最好尽可能少地“取消选中”代码。

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

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

在像 &lt;T&gt; void bindResourceFactory(Class&lt;? extends Factory&lt;T&gt;&gt;) 这样的方法中使用泛型也会再次将您带到同一个地方,因为您将无法使用带有通配符 (Class&lt;? extends Factory&lt;?&gt;&gt;) 扩展 Factory&lt;?&gt; 的类来调用该方法)。

【讨论】:

    【解决方案2】:

    发生此错误的原因是因为编译器没有捕获转换Class&lt;? extends Factory&lt;?&gt;&gt; 中的“内部”通配符(Factory&lt;?&gt; 中的那个)。 (在规范方面,"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&lt;List&lt;?&gt;&gt; 传递给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&lt;List&lt;?&gt;&gt; 传递给采用List&lt;List&lt;T&gt;&gt; 的处理方法。这实际上由编译器完成的方式是它不会将“内部”通配符捕获到某个类型变量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&lt;Factory&lt;?&gt;&gt;,因此它没有理由不能从一个调用返回 Factory&lt;String&gt;,从另一个调用返回 Factory&lt;Double&gt;。因此,我们不应该能够将Supplier&lt;Factory&lt;?&gt;&gt; 捕获到Supplier&lt;Factory&lt;T&gt;&gt;Class.newInstance 将始终返回完全相同类型的对象,但编译器不知道。

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

    如果你想强制它编译,你可以使用未经检查的强制转换,因为 cmets 中的用户建议,但我对所涉及的类知之甚少,无法说明结果是否这实际上证明是正确的。上面的两个代码示例显示了执行类似的操作实际上可能会出错(原则上),但Class 有时是一个特例。

    更合适的修复方法是在bindResourceFactory 上声明一个类型变量,因此它也需要一个Class&lt;? extends Factory&lt;T&gt;&gt;,但我不知道这是否真的适用于您调用该方法的方式.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多