如何将Java通用通配符与具有多个通用参数的方法一起使用?

时间:2012-01-20 00:07:52

标签: java generics subclass bounded-wildcard

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

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

2 个答案:

答案 0 :(得分:2)

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

首先尝试将其明确地转换为Class,如:

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

这样编译器可以将TS绑定到ObjectTI到扩展Object的东西(但它仍会发出警告)。

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);
    }
}

查看here。它提供了Java类型系统的非凡视图(尽管非常密集)。

答案 1 :(得分:0)

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

  

如果表达式名称出现在要进行赋值转换或方法调用转换或转换转换的上下文中,则表达式名称的类型是字段,局部变量或参数之后的声明类型捕获转化§5.1.10)。

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

用于处理此问题的common technique是引入一个将通配符绑定到类型变量的辅助方法:

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

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

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

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