如何在采用多个泛型参数的方法中使用 Java 泛型通配符?
因此,我们有一个像这样的通用方法,它是依赖注入初始化的一部分:
public static <TS, TI extends TS> void registerTransient(
Class<TS> serviceClass, Class<TI> implementationClass)
{
//
}
在某些时候,我们发现类可能不一定存在的情况。它是一个实现类,我们将注入多个关闭(因此服务类与实现类相同。)自然你会这样写:
Class<?> clazz = Class.forName("com.acme.components.MyPersonalImplementation");
registerTransient(clazz, clazz);
IDEA 对此没有问题,但 javac 抱怨:
error: method registerTransient in class TestTrash cannot be applied to given types;
required: Class<TS>,Class<TI>
found: Class<CAP#1>,Class<CAP#2>
reason: inferred type does not conform to declared bound(s)
inferred: CAP#2
bound(s): CAP#1
where TS,TI are type-variables:
TS extends Object declared in method <TS,TI>registerTransient(Class<TS>,Class<TI>)
TI extends TS declared in method <TS,TI>registerTransient(Class<TS>,Class<TI>)
where CAP#1,CAP#2 are fresh type-variables:
CAP#1 extends Object from capture of ?
CAP#2 extends Object from capture of ?
什么给出了?该方法要求第二个参数是第一个参数的子类。无论 ?
是什么类,它的两个参数都是同一个类对象,而且我认为,类总是可以从自身分配。这几乎就像 javac 不必要地发明了第二个通配符类型来用于第二个参数,然后“哦,亲爱的,你这里有两个通配符,所以我无法判断一个是否可以从另一个通配符分配”。
So we have a generic method like this, which is part of dependency injection initialisation:
public static <TS, TI extends TS> void registerTransient(
Class<TS> serviceClass, Class<TI> implementationClass)
{
//
}
At some point we found a case where a class might not necessarily be present. And it's an implementation class which we would be injecting multiple off (so the service class is the same as the implementation class.) Naturally you would write this like this:
Class<?> clazz = Class.forName("com.acme.components.MyPersonalImplementation");
registerTransient(clazz, clazz);
IDEA has no problems with this, but javac complains:
error: method registerTransient in class TestTrash cannot be applied to given types;
required: Class<TS>,Class<TI>
found: Class<CAP#1>,Class<CAP#2>
reason: inferred type does not conform to declared bound(s)
inferred: CAP#2
bound(s): CAP#1
where TS,TI are type-variables:
TS extends Object declared in method <TS,TI>registerTransient(Class<TS>,Class<TI>)
TI extends TS declared in method <TS,TI>registerTransient(Class<TS>,Class<TI>)
where CAP#1,CAP#2 are fresh type-variables:
CAP#1 extends Object from capture of ?
CAP#2 extends Object from capture of ?
What gives? The method requires the second parameter to be a subclass of the first. Irrespective of what class ?
happens to be, it's the same class object for both parameters and a class is, I thought, always assignable from itself. It's almost as if javac is unnecessarily inventing a second wildcard type to use for the second parameter and then going "oh dear, you have two wildcards here, so I can't tell if one is assignable from the other."
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
问题是
Class
不能转换为任何其他类型,除非通过显式转换(它实际上变成Class<#capture-... of ?>
在捕获转换期间)。因此,编译器无法(静态)将这些捕获的类型边界与方法定义的参数化类型进行匹配。首先尝试将其显式转换为 Class,如下所示:
这样编译器可以将 TS 绑定到 Object 并将 TI 绑定到扩展 Object 的对象(它但仍然会发出警告)。
事实上,IntelliJ 的编译器没有抱怨这一点,可能是由于某些优化,甚至可能是编译器错误。您应该将其发布并等待回复。
如果您想用稍微不同的方法检查它,即使它“看起来”没问题,以下内容仍然不会编译:
看看 此处。它呈现了 Java 类型系统的非凡视图(尽管相当密集)。
The problem is that
Class<?>
cannot be cast to any other type unless via an explicit cast (it actually becomesClass<#capture-... of ?>
during capture conversion). As such, the compiler cannot (statically) match the type bounds of those captures with the parameterized types of the method definition.Try casting it explicitly to Class first, like in:
This way the compiler can bind
TS
toObject
andTI
to something that extends Object (it will still emit a warning, though).The fact that IntelliJ's compiler does not complain about this, might be due to some optimization or might even be a compiler bug. You should post it as such and wait for a reply.
If you wish to check it with a slightly different approach, the following will still not compile, even though it "looks" ok:
Have a look in here. It presents an extraordinary view of Java's type system (although quite dense).
您遇到的问题是,根据 JLS 7 §6.5.6.1 简单表达式名称:
在您的例子中,“表达式名称”是标识符
clazz
。正如编译器输出所示,根据 JLS 的要求,它被捕获两次。处理这个问题的常用技术是引入一个辅助方法将通配符绑定到类型变量:
使用通配符调用这个新方法将会起作用:
我看到您在评论中提到了这个解决方法。这可能看起来很奇怪,但这实际上是语言设计者想要的方法。
The issue you're having is that the capture conversion happens individually for each method argument according to JLS 7 §6.5.6.1 Simple Expression Names:
In your case, the "expression name" is the identifier
clazz
. As the compiler output shows, it is being captured twice, as required by the JLS.The common technique for dealing with this is to introduce a helper method that binds the wildcard to a type variable:
Calling this new method with a wildcard will work:
I see that you mentioned this workaround in a comment. It may seem strange, but it's actually the approach intended by the language designers.