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

时间:2015-03-05 00:02:52

标签: java generics type-erasure

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 个答案:

答案 0 :(得分:4)

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

这将打印Hi I'm a Long而不是Hi I'm a Number。您可以看到genericFieldLong,但您无法看到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!");
}

答案 1 :(得分:4)

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

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

你“缺少”的是:你(可以理解)在genericField本身拥有一个对象(带有一个类型)和TestClass有一个对象的事实之间建立了一个错误的联系泛型类型参数。您可以确定genericField s值的类型,并且能够确定为TestClass指定的类型参数。也就是说,虽然您可以根据genericField T 知识推断出类型参数是什么,但这与直接确定T的内容不同1}},这是不可能的。

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

  • 如果TestClass没有T类型的成员,那么您无法提取T。您的代码仅根据您自己的个人知识“确定”T是什么genericField被声明为持有相同类型(因此其中的对象必须属于该类型,因此您可以得出结论泛型参数可能是相同的类型或某种超类型。)

  • 如果您没有使用泛型,genericField只是Object,您仍然可以在genericField中确定对象的类型。也就是说,它的类型与泛型类型“独立”,除非使用泛型,否则编译器会对类型进行约束。编译后它仍然只是一个任意对象,无论你是否使用泛型(这实际上只是一种便利,因为你可以在没有泛型的情况下完成所有这些,只需使用Object和大量的强制转换)。 / p>

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


此外,将上述观点推向家乡:

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

genericLongButNotReally.printTypeInfo();

输出String的信息(这就是为什么会给出那些“未经检查的转换”警告,以防止出现这种奇怪的事情),而不是关心genericLongButNotReally是用{指定Long的事实{1}}类型参数。当您使用泛型类型时,必须克服编译器提供的良好保护;但在运行时它并不关心。

答案 2 :(得分:1)

能够获得变量的类型和对象的类型是两回事。

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