如何在运行时获取通配符上限的类型?

3
假设我正在维护一个某个类 T 的子类注册表:
public class ClassRegistry<T> {
    Set<Class<? extends T>> klasses;
    ...
    public void register(Class<? extends T> klass) {
        klasses.add(klass);
    }

你可以通过 registry.register(this.getClass()) 的方式进行注册。我想通过在 ClassRegistry<T> 上提供一个方法来简化这个过程,让你只需要传递自己即可,例如 registry.register(this):

    public void register(Object obj) {
        Class<?> klass = obj.getClass();
        this.register(klass);
    }

糟糕,这是错误的,因为它在调用自身(匹配参数类型为Object 的重载版本,而不是当然的Class<? extends T>)。 因此,应该改成:

    public void register(Object obj) {
        Class<? extends T> klass = obj.getClass();
        this.register(klass);
    }

当然,现在它不能编译,因为编译器不知道您的obj实际上是某种类型? extends T。而且可能并不知道,因为调用者可能是错误的。
所以我的问题是:如何测试/验证objT的子类,它是通配符上限(因此我可以安全地进行强制转换)?(我怀疑 - 或者说,希望 - 答案涉及Guava的TypeToken,这就是为什么我添加了Guava标签,但我会接受任何答案,谢谢!)

3
为什么register接受一个Object而不是一个T? - Louis Wasserman
有趣的想法 - 如果 T=Class 会怎样? :) - ZhongYu
@LouisWasserman - 因为犯了一个愚蠢错误吗?无论如何,为了知道,忽略那个问题:我仍然想找出它是如何完成的,因为我可能有其他原因需要知道。 - davidbak
1个回答

4

您的register方法不应该能够注册任何类型的对象,这就是Object类型所表示的意义。

我建议将该参数的类型更改为T。这样,您可以保证obj.getClass()将返回一个 Class<? extends T>。虽然编译器不会完全同意,因为Java文档中关于getClass()的说明如下:

实际结果类型为 Class ,其中 |X| 是调用 getClass 的表达式的静态类型的擦除。

调用getClass()的结果不是Class<? extends T>,而是Class<? extends Object>(擦除了T)。您可以将其强制转换为Class<? extends T>,这将生成一个未经检查的转换警告。但是,因为现在您知道obj是一个T,我认为类型安全得到了保留。

@SuppressWarnings("unchecked")
public void register(T obj) {
    Class<? extends T> klass = (Class<? extends T>) obj.getClass();
    this.register(klass);
}

类 klass = obj.getClass(); - ZhongYu
是的,就像我刚才回答@LouisWasserman的问题一样,我因为...嗯,在午餐前或其他原因而错过了这个简单的问题。你的答案是我编写“register”方法的正确方式 - 但我仍然想知道如何获取上限类型... - davidbak
1
@davidbak,答案基本上是你不能,因为存在擦除问题。 - Louis Wasserman
@LouisWasserman 啊,我以为可能会有一些轻微的诀窍,例如你使用TypeToken获取实际类型,但略有不同。 但是现在我想到你所说的,它确实消失了,不是吗? 甚至反射也无法帮助您。 哦,好吧。 - davidbak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接