我有以下接口和返回接口的方法:

public static interface TagMaker<T> {
    public Tag makeTag(String name, T object);
}
//Tags convert the given object to be able to be put in a certain file format.
private static final Map<Class, TagMaker> tagMakers = new HashMap<>();
public static <T> void registerTagMaker(Class<T> clazz, TagMaker<? super T> tagMaker) {
    tagMakers.put(clazz, tagMaker);
}
@SuppressWarnings("unchecked")
private static <T> TagMaker<? super T> getTagMaker(Class<T> clazz) {
    if (clazz == null) return null;
    Class<? super T> candidate = Object.class;
    for (Class c : tagMakers.keySet()) {
        if (c.isAssignableFrom(clazz) && candidate.isAssignableFrom(c)) {//prefer more specific classes; for horizontal relations, choose at random
            candidate = c;
        }
    }
    return tagMakers.get(candidate);
}


我正在尝试获取与任意对象相对应的TagMaker,并使用其制作标签。为什么这行得通

Class clazz = object.getClass();
return getTagMaker(clazz).makeTag(name, object);


但这会引发编译时错误?

return getTagMaker(object.getClass()).makeTag(name, object);
/* results in
error: method makeTag in interface TagMaker<T> cannot be applied to given types;
        return getTagMaker(object.getClass()).makeTag(name, object);
  required: String,CAP#1
  found: String,Object
reason: actual argument Object cannot be converted to CAP#1 by method invocation conversion
  where T is a type-variable:
    T extends Object declared in interface TagMaker
  where CAP#1,CAP#2 are fresh type-variables:
    CAP#1 extends Object super: CAP#2 from capture of ? super CAP#2
    CAP#2 extends Object from capture of ? extends Object
*/

最佳答案

您的示例有:

private static <T> TagMaker<? super T> getTagMaker(Class<T> clazz)


那正确吗?给定代表某种类型T的类实例,它为T的任意任意超类型返回TagMaker。我不知道您的应用程序是什么,但这对我来说没有多大意义。该声明也许可以重写如下:

private static <T> TagMaker<T> getTagMaker(Class<? extends T> clazz)


然后您可以编写:

return getTagMaker(object.getClass()).makeTag(name, object);


如果对象的类型为SomeType,则object.getClass()的类型为Class<? extends SomeType>,则将getTagMaker()的T类型arg推断为SomeType,并且getTagMaker()返回TagMaker<SomeType>。编译时不会出现错误或警告。

除非我完全误解了您要做什么。



更新

好吧,OP添加了一些有用的上下文,并且已经接受了SLaks' answer,但是我将在此处发布一些其他建议,以希望OP或其他人会发现它们有用。

private static final Map<Class<?>, TagMaker<?>> tagMakers = new HashMap<>();


我已将此声明从原始类型更改为无界通配符,只是为了将其保留在通用类型系统中。这是一个有趣的情况,让人想起Bloch的Effective Java项目29中描述的“类型安全的异类容器”。关键是不能使用Java的泛型来描述键和值之间的关系。

// original
public static <T> void registerTagMaker(Class<T> clazz, TagMaker<? super T> tagMaker) {
    tagMakers.put(clazz, tagMaker);
}

// my suggestion
public static <T> void registerTagMaker(Class<? extends T> clazz, TagMaker<T> tagMaker) {
    tagMakers.put(clazz, tagMaker);
}


这两种方法都将起作用,因为它会在类和该类的TagMaker之间建立所需的关系。问题是您想用哪种方式思考T。实际上,这并不重要,因为在将项目放入Map中时,这种类型关系被丢弃了,而后者是完全通配的。

private static <T> TagMaker<T> getTagMaker(Class<? extends T> clazz) {
    if (clazz == null) return null;
    Class<?> candidate = Object.class;
    for (Class<?> c : tagMakers.keySet()) {
        if (c.isAssignableFrom(clazz) && candidate.isAssignableFrom(c)) {//prefer more specific classes; for horizontal relations, choose at random
            candidate = c;
        }
    }

    @SuppressWarnings("unchecked")
    TagMaker<T> result = (TagMaker<T>)tagMakers.get(candidate);
    return result;
}


我在这里做了几件事。首先,我将clazz参数更改为Class<? extends T>,并将返回类型更改为TagMaker<T>。其次,我将局部变量更改为Class<?>。这段代码对这里使用的值的类型关系做出了很多断言,我认为这些值在通用类型系统中是无法表达的,因此对本地人使用Class<? super T>除了添加警告外不做任何事情。最后,我对从地图中检索到的值进行了强制转换。这是生成警告的行,因此我已将其分配给本地并添加了@SuppressWarnings注释(只能添加到声明中)。这是安全的,因为它依赖于registerTagMaker()放入地图中项目的关系。

为什么不返回TagMaker<? super T>?这将起作用,并且是正确的(至少,这是不正确的),但是这会使API令人困惑,并且我认为它不会增加任何价值。考虑原始的TagMaker定义:

public static interface TagMaker<T> {
    public Tag makeTag(String name, T object);
}


这意味着您可以使用T或T的任何子类型调用makeTag。现在,我们使用返回TagMaker<? super T>的getTagMaker()版本:

TagMaker<? super Foo> tmfoo = getTagMaker(Foo.class);
tmfoo.makeTag("", ???);


作为makeTag()的第二个参数,我可以传递什么?我可以传递任何作为Foo未知超类型的子类型的东西。实际上,这意味着我只能传递Foo类型或Foo子类型的东西。因此,返回TagMaker<? super Foo>根本无法帮助调用者,因此您最好返回TagMaker<Foo>。我认为这要清楚得多。

关于java - 为什么只有在将类存储在单独的变量中时,这些泛型才起作用?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/20449771/

10-12 14:51