2

所以我们有一个像这样的通用方法,它是依赖注入初始化的一部分:

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 不必要地发明了第二个通配符类型来用于第二个参数,然后说“哦,亲爱的,你这里有两个通配符,所以我不知道一个是否可以从另一个分配。”

4

2 回答 2

2

问题是除非通过显式强制转换(它实际上是在捕获转换期间Class<?>变为),否则不能转换为任何其他类型。Class<#capture-... of ?>因此,编译器不能(静态地)将这些捕获的类型边界与方法定义的参数化类型匹配。

尝试首先将其显式转换为 Class ,例如:

registerTransient((Class<Object>)clazz, clazz); 

这样,编译器可以绑定TS到扩展 ObjectObjectTI东西(尽管它仍然会发出警告)。

IntelliJ 的编译器没有抱怨这一点,这可能是由于一些优化,甚至可能是编译器错误。您应该这样发布并等待回复。

如果您希望用稍微不同的方法检查它,即使它“看起来”没问题,以下内容仍然无法编译:

public class A {
    static class B {}

    static class C extends B {}

    static <T, R extends T> void method(final Class<T> t, final Class<R> r) {}

    public static final void main(String... args) {
        B b = new B();
        C c = new C();
        Class<?> cb = b.getClass();
        Class<?> cc = c.getClass();
        method(cb, cc);
    }
}

看看这里。它展示了 Java 类型系统的非凡视图(尽管非常密集)。

于 2012-01-20T02:15:51.470 回答
0

您遇到的问题是捕获转换根据 JLS 7 §6.5.6.1 Simple Expression Names为每个方法参数单独发生:

如果表达式名称出现在要进行赋值转换或方法调用转换或强制转换的上下文中,则表达式名称的类型是捕获转换后的字段、局部变量或参数的声明类型(第5.1 节)。 10 )。

在您的情况下,“表达式名称”是 identifier clazz。正如编译器输出所示,它被捕获了两次,这是 JLS 要求的。

处理这个问题的常用技术是引入一个将通配符绑定到类型变量的辅助方法:

private static <T> void registerTransient(Class<T> serviceAndImplClass)
{
    registerTransient(serviceAndImplClass, serviceAndImplClass);
}

使用通配符调用这个新方法将起作用:

Class<?> clazz = Class.forName("com.acme.components.MyPersonalImplementation");
registerTransient(clazz);

我看到你在评论中提到了这个解决方法。这可能看起来很奇怪,但它实际上是语言设计者想要的方法。

于 2013-07-03T03:55:55.340 回答