简体   繁体   English

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

[英]Java generics, type erasure and type of a generic member

Java has type erasure and people say that one can't determine the type of a generic object at runtime without hacks. Java有类型擦除,人们说在运行时无法确定通用对象的类型而没有黑客攻击。 Consider the code below 请考虑以下代码

    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();
    }
}

It gives me the following result: 它给了我以下结果:

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

Seems like type information is readily available at runtime. 似乎类型信息在运行时很容易获得。 What am I missing here? 我在这里错过了什么?

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

This will print Hi I'm a Long instead of Hi I'm a Number . 这将打印Hi I'm a Long而不是Hi I'm a Number You can see that genericField is a Long but you can't see that T was instantiated as Number . 您可以看到genericFieldLong但您无法看到T被实例化为Number

Here's an example of something you can't do because of type erasure. 这是一个由于类型擦除而无法做的事情的例子。

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

You can determine the type of any given object in genericField at runtime, but you cannot determine the difference between a TestClass<X> and a TestClass<Y> at runtime without examining some members that you know happen to be constrained by the generic type. 您可以在运行时确定genericField中任何给定对象的类型,但是您无法在运行时确定TestClass<X>TestClass<Y>之间的差异,而无需检查您知道碰巧受泛型类型约束的某些成员。 That is you cannot determine the type parameter of a TestClass<...> given an instance of a TestClass alone. 也就是说,如果仅给出一个TestClass实例,则无法确定TestClass<...>的类型参数。

Your code displays the type of genericField 's value, not the parameterized type of the TestClass instance. 您的代码显示genericField的值的类型, 而不是 TestClass实例的参数化类型。 Try printing this.getClass() and you'll see its identical in both cases. 尝试打印this.getClass() ,你会发现它在两种情况下都是相同的。

What you are "missing" is this: You are (understandably) making an incorrect connection between the fact that genericField itself holds an object (with a type) and the fact that TestClass has a generic type parameter. 你“缺少”的是这样的:你(可以理解)在genericField本身拥有一个对象(带有一个类型)和TestClass有一个泛型类型参数这一事实之间建立了一个不正确的联系。 You are confusing the ability to determine the type of genericField s value with the ability to determine the type parameter specified to a TestClass . 您可以通过确定为TestClass指定的类型参数的能力来确定genericField值的类型。 That is, while you can deduce what the type parameter was based on your knowledge that genericField is T , this is not the same as directly being able to determine what T was, which is impossible. 也就是说,虽然您可以根据genericFieldT知识推断出类型参数是什么,但这与直接确定T是什么不同,这是不可能的。

Another way to look at the previous paragraph is to consider these points: 查看上一段的另一种方法是考虑以下几点:

  • If TestClass had no members of type T then there's no other way you could extract T . 如果TestClass没有类型为T成员,则没有其他方法可以提取T Your code only "determines" what T was based on your own personal knowledge that genericField was declared as holding that same type (and therefore the object in it must be of that type, and therefore you can conclude that the generic parameter was likely that same type or some supertype of it). 您的代码仅“确定” T基于您自己的个人知识,即genericField被声明为持有相同类型(因此其中的对象必须属于该类型,因此您可以得出结论通用参数可能是相同的)类型或某种超类型)。

  • If you did not use generics, and genericField was just an Object , you'd still be able to determine the type of the object in genericField . 如果你没有使用泛型,而genericField只是一个Object ,你仍然可以在genericField确定对象的类型。 That is, its type is "independent" of the generic type, except when you use generics the compiler places a constraint on the type. 也就是说,它的类型与泛型类型“独立”,除非使用泛型,否则编译器会对类型进行约束。 It's still just an arbitrary object after compilation, regardless of whether or not you used generics (which are really just a convenience, as you could do all of this without generics and just use Object and lots of casts instead). 编译后它仍然只是一个任意对象,无论你是否使用泛型(这实际上只是一种方便,因为你可以在没有泛型的情况下完成所有这些,只需使用Object和大量的强制转换)。

Consider also the possibility of a TestClass<Base> , where genericField was assigned a Derived . 还要考虑TestClass<Base>的可能性,其中genericField被赋予了Derived Your code would correctly show that genericField was a Derived , but you have no way of knowing that the type parameter was a Base vs. a Derived , because the information was erased. 您的代码将正确显示genericFieldDerived ,但您无法知道类型参数是BaseDerived ,因为信息已被删除。


Also, to ram the above points home even further: 此外,将以上几点进一步归结为家:

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

genericLongButNotReally.printTypeInfo();

Outputs the info for a String (this is why those "unchecked conversion" warnings are given, to prevent strange things like this), not caring about the fact that genericLongButNotReally was specified with a Long type parameter. 输出String的信息(这就是给出那些“未经检查的转换”警告的原因,以防止出现这种奇怪的事情),而不关心使用Long类型参数指定genericLongButNotReally的事实。 The kludge is necessary to circumvent the good protection that the compiler offers when you use generic types; 当您使用泛型类型时,必须克服编译器提供的良好保护; but at runtime it does not care. 但在运行时它并不关心。

Being able to get the type of a variable and the type of an object are two different things. 能够获得变量的类型和对象的类型是两回事。

Just because you can get the type of genericField doesn't mean that you can see that T was a number. 仅仅因为你可以得到genericField的类型并不意味着你可以看到T是一个数字。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM