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
的类型参数的能力。也就是说,虽然您可以根据 您的 知识推断出类型参数是什么 genericField
是 T
,但这与直接能够确定不同T
是什么,这是不可能的。
查看上一段的另一种方法是考虑以下几点:
如果 TestClass
没有 T
类型的成员,那么您没有其他方法可以提取 T
。您的代码仅 "determines" 什么 T
是基于您自己的个人知识,即 genericField
被声明为持有相同类型(因此其中的对象必须是该类型,因此您可以得出结论,泛型参数可能是同一类型或它的某些超类型)。
如果你没有使用泛型,而genericField
只是一个Object
,你仍然可以确定[=11=中对象的类型].也就是说,它的类型是泛型类型的 "independent" ,除非您使用泛型,编译器会对该类型施加约束。编译后它仍然只是一个任意对象,无论您是否使用泛型(这实际上只是一种方便,因为您可以在没有泛型的情况下完成所有这些操作,而只需使用 Object
和大量强制转换)。
还考虑 TestClass<Base>
的可能性,其中 genericField
被分配了 Derived
。您的代码会正确显示 genericField
是 Derived
,但您无法知道类型参数是 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
类型参数。当您使用泛型类型时,为了规避编译器提供的良好保护,必须进行拼凑;但在运行时它并不关心。
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
的类型参数的能力。也就是说,虽然您可以根据 您的 知识推断出类型参数是什么 genericField
是 T
,但这与直接能够确定不同T
是什么,这是不可能的。
查看上一段的另一种方法是考虑以下几点:
如果
TestClass
没有T
类型的成员,那么您没有其他方法可以提取T
。您的代码仅 "determines" 什么T
是基于您自己的个人知识,即genericField
被声明为持有相同类型(因此其中的对象必须是该类型,因此您可以得出结论,泛型参数可能是同一类型或它的某些超类型)。如果你没有使用泛型,而
genericField
只是一个Object
,你仍然可以确定[=11=中对象的类型].也就是说,它的类型是泛型类型的 "independent" ,除非您使用泛型,编译器会对该类型施加约束。编译后它仍然只是一个任意对象,无论您是否使用泛型(这实际上只是一种方便,因为您可以在没有泛型的情况下完成所有这些操作,而只需使用Object
和大量强制转换)。
还考虑 TestClass<Base>
的可能性,其中 genericField
被分配了 Derived
。您的代码会正确显示 genericField
是 Derived
,但您无法知道类型参数是 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
类型参数。当您使用泛型类型时,为了规避编译器提供的良好保护,必须进行拼凑;但在运行时它并不关心。