Java泛型,类型擦除以及泛型成员的类型

4

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

似乎运行时可以轻易获取类型信息。我错过了什么吗?
3个回答

4
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!");
}

非常清晰;值得注意的是,在printTypeInfo()中打印this.getClass()将为两个测试用例产生相同的结果,这有助于突出显示确定genericField值类型的能力与确定TestClass实例类型参数的能力之间的差异。 - Jason C

4
您可以在运行时确定genericField中任何给定对象的类型,但是您不能在不检查一些由泛型类型约束的成员的情况下确定TestClass<X>TestClass<Y>之间的区别。也就是说,仅凭TestClass的实例无法确定TestClass<...>的类型参数。

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

您“缺少”的是:您(可以理解为)在genericField本身持有一个具有类型的对象和TestClass具有泛型类型参数之间建立了不正确的联系。您混淆了能够确定genericField值的类型与能够确定指定为TestClass的类型参数的能力。也就是说,虽然您可以根据您知道的genericFieldT来推断出类型参数是什么,但这并不等于直接能够确定T是什么,这是不可能的。

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

  • 如果TestClass没有类型为T的成员,则无法提取T。您的代码仅基于您自己的个人知识确定genericField声明为持有相同类型(因此其中的对象必须是该类型,并且因此您可以得出泛型参数可能是该类型或某个其超类型)。

  • 如果您没有使用泛型,并且genericField只是一个Object,则仍然可以确定genericField中对象的类型。也就是说,它的类型与泛型类型“无关”,除非您使用泛型,编译器才会对类型进行约束。编译后,它仍然只是任意对象,无论您是否使用泛型(实际上,您可以在不使用泛型的情况下完成所有这些操作,只需使用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();

输出一个字符串的信息(这就是为什么会出现“未经检查的转换”警告,以防止类似这样的奇怪事情发生),而不关心指定了一个Long类型参数的genericLongButNotReally。这个方法是必要的,以绕过编译器在使用泛型类型时提供的良好保护措施;但在运行时它并不关心。

1
能够获取变量的类型和对象的类型是两种不同的事情。 仅仅因为你可以获取genericField的类型,并不意味着你能看到T是一个数字。

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