Java 泛型、类型擦除和泛型成员的类型

Java generics, type erasure and type of a generic member

Java 具有类型擦除功能,人们说如果不进行黑客攻击,就无法在运行时确定通用对象的类型。考虑下面的代码

    public class TestClass<T> {

    private T genericField;

    public TestClass(T genericField) {
        this.genericField = genericField;
    }

    public void printTypeInfo() {
        System.out.println("Hi I'm a " + genericField.getClass());
        System.out.println("Am I a string? " + (genericField instanceof String));
        System.out.println("Am I a long? " + (genericField instanceof Long));
    }

    public static void main(String [] args) {
        TestClass<String> genericString = new TestClass<>("Hello");
        TestClass<Long> genericLong = new TestClass<>(111111L);
        genericString.printTypeInfo();
        System.out.println("------------------");
        genericLong.printTypeInfo();
    }
}

它给了我以下结果:

Hi I'm a class java.lang.String
Am I a string? true
Am I a long? false
------------------
Hi I'm a class java.lang.Long
Am I a string? false
Am I a long? true

似乎类型信息在运行时很容易获得。我在这里错过了什么?

TestClass<Number> genericNumber = new TestClass<>(42L);
genericNumber.printTypeInfo();

这将打印 Hi I'm a Long 而不是 Hi I'm a Number。你可以看到 genericField 是一个 Long 但你看不到 T 被实例化为 Number.

下面是一个由于类型擦除而无法执行的示例。

TestClass<?> generic = new TestClass<String>("Hello");

if (generic instanceof TestClass<String>) {
    System.out.println("It holds a string!");
}
else if (generic instanceof TestClass<Long>) {
    System.out.println("It holds a long!");
}

能够获取变量的类型和对象的类型是两个不同的东西。

仅仅因为您可以获得 genericField 的类型并不意味着您可以看到 T 是一个数字。

您可以在运行时确定 genericField 中任何给定对象的类型,但是如果不检查某些成员,则无法在运行时确定 TestClass<X>TestClass<Y> 之间的区别您知道碰巧受到泛型类型的约束。也就是说,您无法单独给定 TestClass 的实例来确定 TestClass<...> 的类型参数。

您的代码显示 genericField 值的类型, 而不是 TestClass 实例的参数化类型。尝试打印 this.getClass() ,您会发现两种情况下的结果完全相同。

你是什么 "missing" 是这样的:你(可以理解)在 genericField 本身拥有一个对象(具有类型)这一事实与 TestClass 有一个泛型类型参数。您混淆了确定 genericField 值类型的能力与确定指定给 TestClass 的类型参数的能力。也就是说,虽然您可以根据 您的 知识推断出类型参数是什么 genericFieldT,但这与直接能够确定不同T 是什么,这是不可能的。

查看上一段的另一种方法是考虑以下几点:

  • 如果 TestClass 没有 T 类型的成员,那么您没有其他方法可以提取 T。您的代码仅 "determines" 什么 T 是基于您自己的个人知识,即 genericField 被声明为持有相同类型(因此其中的对象必须是该类型,因此您可以得出结论,泛型参数可能是同一类型或它的某些超类型)。

  • 如果你没有使用泛型,而genericField只是一个Object,你仍然可以确定[=11=中对象的类型].也就是说,它的类型是泛型类型的 "independent" ,除非您使用泛型,编译器会对该类型施加约束。编译后它仍然只是一个任意对象,无论您是否使用泛型(这实际上只是一种方便,因为您可以在没有泛型的情况下完成所有这些操作,而只需使用 Object 和大量强制转换)。

还考虑 TestClass<Base> 的可能性,其中 genericField 被分配了 Derived。您的代码会正确显示 genericFieldDerived,但您无法知道类型参数是 Base 还是 Derived,因为信息是已删除。


此外,为了进一步强调以上几点:

TestClass<String> genericString = new TestClass<String>("Hello");
TestClass<?> kludge = genericString;
TestClass<Long> genericLongButNotReally = (TestClass<Long>)kludge;

genericLongButNotReally.printTypeInfo();

输出 String 的信息(这就是给出 "unchecked conversion" 警告的原因,以防止出现此类奇怪的事情),而不关心 genericLongButNotReally 被指定的事实使用 Long 类型参数。当您使用泛型类型时,为了规避编译器提供的良好保护,必须进行拼凑;但在运行时它并不关心。