Java:T obj; obj.getClass() 的类型是 Class <?> 而不是 Class <? extends T>。为什么?

10
在这样的函数中:
<T> void foo(T obj)
obj.getClass()的类型是Class<?>而不是Class<? extends T>。为什么呢?
下面这段代码可以正常工作:
String foo = "";
Class<? extends String> fooClass = foo.getClass();

T#getClass() 的签名似乎返回一个 Class<? extends T>,是吗?

如果 T 确实是一个泛型,为什么签名会不同呢?

为了解决这个问题(并更清楚地说明我在思考什么),我实现了这个函数:

@SuppressWarnings("unchecked") static <T> Class<? extends T> classOf(T obj) {
    return (Class<? extends T>) obj.getClass();
}

再次提问:为什么在字符串的情况下不需要转换,而在这里需要转换?为什么需要SuppressWarnings?从代码中不是总能清楚地知道它能够安全地执行此转换吗?
有没有办法从obj获取Class<? extends T>?如果有,如何获取?如果没有,为什么没有?
一种方法是使用classOf。这样是安全的,对吗?如果始终如此安全并提供了一种真正获取Class<? extends T>(而不是Class<?>)的安全方式,为什么Java中没有这样的函数?还是有吗?
那种情况怎么样?
<T> void bar(T[] array)

array.getClass().getComponentType() 再次返回一个 Class<?> 而不是 Class<? extends T>。为什么呢?

我实现了这个函数:

@SuppressWarnings("unchecked") static <T> Class<? extends T> classOf(T[] array) {
    return (Class<? extends T>) array.getClass().getComponentType();
}

这个还能安全使用吗?


更明确地说,考虑以下演示代码:

static interface I<T> {
    Class<? extends T> myClass();
}

static class A implements I<A> {
    public Class<? extends A> myClass() {
        return this.getClass();
    }
}

static <T> void foo(I<T> obj) {
    Class<? extends T> clazz = obj.myClass(); // this works
}

这个运行良好。但是对于 Object#getClass() 来说并非如此。
为什么不能例如有一个通用接口像 ClassInstance<T> ,带有函数 getClass(), 并且每个Java对象都自动实现它? 这将比将其从非泛型基类 Object 扩展出来的解决方案具有正好那些改进。
或者将 Object 作为泛型类:
static abstract class Object<T> {
    abstract Class<? extends T> myClass();
}

static class B extends Object<B> {
    public Class<? extends B> myClass() {
        return this.getClass();
    }
}

static <T> void bar(Object<T> obj) {
    Class<? extends T> clazz = obj.myClass(); // this works
}

现在将myClass()视为getClass(),并考虑编译器会自动将其添加到每个类中。这将解决很多强制转换问题。我所谈的主要问题是:为什么不这样做呢?
或者用不同的话来说:在这里,我详细描述了解决这种classOf函数的问题的解决方案。 为什么不这样做,即为什么原始函数不是这样的?
(我不希望得到这样的答案:Java现在的工作方式,即从非泛型的Object扩展并定义此函数,使其不可能。我想知道为什么不能以其他方式解决它,以便它成为可能。)

回答你的编辑...那样是无法编译通过的,因为即使 A 是 B 的子类,Class<A> 不是 Class<B> 的子类。你会得到不兼容的返回类型。- 唯一能让它工作的方式是如果所有东西都返回 Class<? extends Object>,这基本上就是现在的情况。 - CurtainDog
@CurtainDog:这两个例子都可以编译通过(将第二个示例中的 Object 更改为其他内容)。但我的问题实际上不是关于如何做到这一点,而是更多地关于为什么会是这样。已经有可能(例如我展示的方式)使 getClass() 返回正确的类型。那为什么不是这样设计的呢? - Albert
我认为当他们修补编译器以从x.getClass()返回Class <? extends X>时,这只是被简单地忽略了。 - Paŭlo Ebermann
4个回答

6
基本问题在于getClass()方法不能返回类本身,因为它是在Object级别上定义的。也就是说,它仅被定义为扩展对象的类。他们本可以像这样定义getClass():
Class<this> getClass() { /**/ }

但实际上它的
Class<?> getClass()

这意味着泛型无法理解getClass返回的内容。

他们本可以这样定义[...]。实际上,你的回答已经接近我想知道的问题的"为什么"了。为什么不像这样(或类似的方式)进行,这样我所要求的就可能会实现了(因为Java本身实际上允许我所需要的--参见我的例子)。 - Albert
1
Java之所以没有在泛型中返回“此类型”的机制,原因是什么呢?你需要向Java的设计者询问这个问题。 - ILMTitan
我已经实现了接口,如果有一个返回类型的话,这将非常有用。相反,我不得不使用数百个方法(成千上万)的协变返回类型。 - Peter Lawrey

4

在Java中,泛型只是更加安全的开发工具。

JVM不知道任何关于泛型的信息。 Java编译器会在运行时丢弃这些信息,所有的泛型类型都被当作Object引用来处理。为了弥补这一点,编译器插入必要的转换。 这个过程被称为类型擦除(请搜索谷歌!)。

List<String> x = new ArrayList<String>();
x.add("hello");
String hello = x.get(0);

在运行时,它将变为以下内容。
List x = new ArrayList();
x.add("hello");
String hello = (String) x.get(0);

要解决您的问题,您可以尝试调查数组中的各个元素(arr[0].getClass())。


1
为了解决您的问题,您可以尝试调查数组中的各个元素。只要您的数组中有元素!但是对于这种讨论,我给一个赞。 - Kirk Woll
3
数组本身难道不必须知道它是什么吗?它可以抛出 ArrayStoreException 异常,因此信息必须存在于某个地方。如果它在编译时是一个 T[],理论上编译器应该能够安全地获取 Class<T> - Albert
阿尔伯特:你确定它不只是一个 Object[] 吗? - Gabe
小问题:编译器在类字节码中留下了一些泛型信息的残留。具体来说,字段泛型类型、方法泛型类型(参数、返回类型)和超类型泛型类型替换是被保留并且可以访问的。但是确实如此,Object实例没有泛型信息,只有类描述。 - StaxMan

3
这里有几个不完全准确的回答。泛型确实是使用类型擦除(type erasure) 实现的,但这并不意味着所有的类型信息都丢失了。编译器会将类型擦除到最低限度。
所以<T extends String>会被擦除为String;这就是为什么字符串的getClass返回Class<? extends String>。但是没有边界<T>会被擦除为Object;所以getClass返回Class<? extends Object>,即Class<?>。
泛型很复杂,它们并不总是按你的意愿运行,但是有许多方法可以解决这些问题(通过改进边界、通过反射访问运行时类型信息和传递类对象等)。类型擦除实际上是一种相当聪明的解决方案,不应该受到太多负面评价。

啊,这很有趣!但我仍然想知道为什么会这样(因为我没有看到在类型擦除的情况下限制getClass()的具体原因)。我扩展了我的原始问题以展示我的意思。 - Albert

0
由于类型擦除,运行时不会为泛型数组保留类型信息。实际上,JVM在内部将所有泛型数组都视为Object[]。
如果您想获取运行时类型,最好的选择可能只是在数组中的第一个项目上调用getClass()。您显然需要找到一种处理空情况和包含多种类型对象的情况的方法等等。

但是array.getClass().getComponentType()会返回真实的类,不是吗?所以它在运行时实际上知道数组类型。 - Albert
如果您创建了特定类型的数组,例如new Integer[] {1,2,3,4},它将返回真实类。但是,如果您创建了一个泛型数组,例如SomeCollection<T>类中的T[],则只会得到一个java.lang.Object数组。 - mikera
1
是的,如果您调用我的原始问题中的示例函数,它必须是特定类型。我在这里也假设了这种情况,但我认为根本不可能没有这种情况。创建通用数组也根本不可能。 - Albert

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