声明类型参数为 <T extends A & B & C> 的类时,类型擦除过程将使用类型 A 替换 T . 但是如果 T 类型的变量调用在接口 B 或 C 中声明的方法,Java会用它做什么?
<T extends A & B & C>
A
T
B
C
当我们创建一个泛型类型的实例,如 ArrayList<String> 时,类型参数 String 也将被删除,但调用 get 方法将返回类型 String ,这些信息来自哪里,因为它已被删除?
ArrayList<String>
String
get
我知道使用java反射,但我需要一些具体的解释 .
不使用反射 - 铸造是 . 例如,请使用以下代码:
interface A { void a(); } interface B { void b(); } interface C { void c(); } class Generic<T extends A & B & C> { T t; Generic(T t) { this.t = t; } void callMethods() { t.a(); t.b(); t.c(); } }
现在看一下 Generic 的字节码(删除了构造函数):
Generic
class Generic extends java.lang.Object{ A t; void callMethods(); Code: 0: aload_0 1: getfield #2; //Field t:LA; 4: invokeinterface #3, 1; //InterfaceMethod A.a:()V 9: aload_0 10: getfield #2; //Field t:LA; 13: checkcast #4; //class B 16: invokeinterface #5, 1; //InterfaceMethod B.b:()V 21: aload_0 22: getfield #2; //Field t:LA; 25: checkcast #6; //class C 28: invokeinterface #7, 1; //InterfaceMethod C.c:()V 33: return }
在每个invokeinterface调用 b() 和 c() 之前,请注意checkcast指令 .
b()
c()
结果就像 Generic 实际上是这样编写的:
class Generic<T extends A> { T t; Generic(T t) { this.t = t; } void callMethods() { t.a(); ((B) t).b(); ((C) t).c(); } }
至于你关于 ArrayList 的问题 - 有关作为列表元素类型的 get() 的返回类型的信息仍然存储为 ArrayList 类的一部分 . 编译器将再次在调用代码中插入强制转换,因此:
ArrayList
get()
ArrayList<String> strings = new ArrayList<String>(); strings.add("foo"); String x = strings.get(0);
在执行时相当于:
ArrayList strings = new ArrayList(); strings.add("foo"); String x = (String) strings.get(0);
这样做的一个重要方面是你不能在执行时询问 ArrayList 对象 T 是什么 - 该信息已被删除 .
在编译器完成类型检查后,类型擦除有效地用 Object 替换所有泛型类型参数 . 在您的 <T extends A & B & C> 示例中, A 就像其他所有内容一样被删除 .
Object
当您在 ArrayList<String> 上调用 get 时,编译器会生成字节码,将返回的对象自动转换为字符串 . 列表本身并不是一个字符串列表(由于类型擦除),但是调用 get 的代码知道它希望从列表中获取的东西是一个字符串 .
2 回答
不使用反射 - 铸造是 . 例如,请使用以下代码:
现在看一下
Generic
的字节码(删除了构造函数):在每个invokeinterface调用
b()
和c()
之前,请注意checkcast指令 .结果就像
Generic
实际上是这样编写的:至于你关于
ArrayList
的问题 - 有关作为列表元素类型的get()
的返回类型的信息仍然存储为ArrayList
类的一部分 . 编译器将再次在调用代码中插入强制转换,因此:在执行时相当于:
这样做的一个重要方面是你不能在执行时询问
ArrayList
对象T
是什么 - 该信息已被删除 .在编译器完成类型检查后,类型擦除有效地用
Object
替换所有泛型类型参数 . 在您的<T extends A & B & C>
示例中,A
就像其他所有内容一样被删除 .当您在
ArrayList<String>
上调用get
时,编译器会生成字节码,将返回的对象自动转换为字符串 . 列表本身并不是一个字符串列表(由于类型擦除),但是调用get
的代码知道它希望从列表中获取的东西是一个字符串 .