使用运行时确定的 class 调用泛型方法
Call generic method with class determined at runtime
我正在尝试使用 jersey 2/HK2 将工厂 classes 与特定注释自动绑定。因此,我在运行时从通用接口获取提供的类型,然后尝试将工厂绑定到该类型。将工厂绑定到 class 的方法如下所示:
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
之外,还有什么方法可以使这项工作正常进行吗?
发生此错误的原因是因为编译器没有捕获转换 Class<? extends Factory<?>>
中的 "inner" 通配符(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>>
的处理方法。编译器实际实现的方式是它不会将 "inner" 通配符捕获到某些类型变量 T
.
由于非常相似的原因,问题中的代码无法编译。由于 Class
上的类型参数主要与构造函数相关的方法相关(特别是 newInstance
方法),我们可以使用 Supplier
:[=36= 来展示一个更相似的示例]
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<?>>
,所以没有理由不能,比如说,return 来自一次调用的 Factory<String>
和 Factory<Double>
来自另一个。因此,我们不应该能够捕获 Supplier<Factory<?>>
到 Supplier<Factory<T>>
。 Class.newInstance
将始终 return 完全相同类型的对象,但编译器不知道。
我认为 Eclipse 的编译器在这种情况下可能只是错误地编译了问题中的代码。
如果你想强制编译它,你可以可以使用未经检查的强制转换,正如评论中的用户所建议的那样,但我对 类 涉及说结果是否真的可以证明是正确的。上面的两个代码示例展示了如何做这样的事情实际上可能会出错(原则上),但 Class
有时是一种特殊情况。
一种更合适的修复方法是在 bindResourceFactory
上声明一个类型变量,因此它也需要一个 Class<? extends Factory<T>>
,但我不知道这是否真的适用于这种方式你正在调用方法。
发生这种情况是因为 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。
你终于可以抑制转换警告了,最好'uncheck'代码越少越好。
@SuppressWarnings({ "unchecked" })
Class<? extends Factory<Object>> objFactory = (Class<? extends Factory<Object>>) factory;
bindFactory(objFactory).to(providedClass).in(Singleton.class);
需要考虑的一件重要事情是方法 getProvidedClass(...)
应该 return class 正确
此外,在这样的方法中使用泛型 <T> void bindResourceFactory(Class<? extends Factory<T>>)
会再次将您带到同一个地方,因为您将无法使用 class 扩展 Factory<?>
带通配符 (Class<? extends Factory<?>>
).
我正在尝试使用 jersey 2/HK2 将工厂 classes 与特定注释自动绑定。因此,我在运行时从通用接口获取提供的类型,然后尝试将工厂绑定到该类型。将工厂绑定到 class 的方法如下所示:
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
之外,还有什么方法可以使这项工作正常进行吗?
发生此错误的原因是因为编译器没有捕获转换 Class<? extends Factory<?>>
中的 "inner" 通配符(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>>
的处理方法。编译器实际实现的方式是它不会将 "inner" 通配符捕获到某些类型变量 T
.
由于非常相似的原因,问题中的代码无法编译。由于 Class
上的类型参数主要与构造函数相关的方法相关(特别是 newInstance
方法),我们可以使用 Supplier
:[=36= 来展示一个更相似的示例]
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<?>>
,所以没有理由不能,比如说,return 来自一次调用的 Factory<String>
和 Factory<Double>
来自另一个。因此,我们不应该能够捕获 Supplier<Factory<?>>
到 Supplier<Factory<T>>
。 Class.newInstance
将始终 return 完全相同类型的对象,但编译器不知道。
我认为 Eclipse 的编译器在这种情况下可能只是错误地编译了问题中的代码。
如果你想强制编译它,你可以可以使用未经检查的强制转换,正如评论中的用户所建议的那样,但我对 类 涉及说结果是否真的可以证明是正确的。上面的两个代码示例展示了如何做这样的事情实际上可能会出错(原则上),但 Class
有时是一种特殊情况。
一种更合适的修复方法是在 bindResourceFactory
上声明一个类型变量,因此它也需要一个 Class<? extends Factory<T>>
,但我不知道这是否真的适用于这种方式你正在调用方法。
发生这种情况是因为 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。
你终于可以抑制转换警告了,最好'uncheck'代码越少越好。
@SuppressWarnings({ "unchecked" })
Class<? extends Factory<Object>> objFactory = (Class<? extends Factory<Object>>) factory;
bindFactory(objFactory).to(providedClass).in(Singleton.class);
需要考虑的一件重要事情是方法 getProvidedClass(...)
应该 return class 正确
此外,在这样的方法中使用泛型 <T> void bindResourceFactory(Class<? extends Factory<T>>)
会再次将您带到同一个地方,因为您将无法使用 class 扩展 Factory<?>
带通配符 (Class<? extends Factory<?>>
).