java - 为什么只有当我将类存储在一个单独的变量中时这些泛型才起作用?

标签 java generics

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

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 的类实例,它返回一个 TagMaker T 的任意父类(super class)型?我不知道你的应用程序是什么,但这对我来说没有多大意义。也许这个声明可以重写如下:

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 类型参数被推断为 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>对于本地人,除了添加警告外什么都不做。最后,我对从 map 中检索到的值进行了强制转换。这是生成警告的行,因此我已将其分配给本地并添加了 @SuppressWarnings注释(只能添加到声明中)。这是安全的,因为它依赖于通过 registerTagMaker() 放入 map 的项目之间的关系。

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

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

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

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

我可以将什么作为第二个参数传递给 makeTag()?我可以传递任何未知父类(super class)型 Foo 的子类型。实际上,这意味着我只能传递 Foo 类型或 Foo 的子类型。所以返回TagMaker<? super Foo>根本没有帮助调用者,所以你还不如返回 TagMaker<Foo>反而。在我看来,这要清楚得多。

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

相关文章:

java - 如何在 Spring Boot 应用程序中将数据库架构更改从源数据库同步到目标数据库

java - 如何在 Hibernate 中更新 TimerTask 中的实体

java - 使用JAVA形成测试数据列表进行selenium功能测试

C# - 代码有什么问题(泛型、静态方法等)

c# - 如何检查 IEnumerable<T> 中的 T 是否为接口(interface)?

java - 无法在 Glassfish 上部署的应用程序中发送电子邮件

java - 如何使用 javadb JUnit 测试 jpa 代码而不修改原始数据库?

c# - 为什么在通用参数约束中强制执行某些顺序?

swift - 在 Swift 中实例化关联类型的数组

c# - 确定类型是否是泛型类型的子类