声明具有类型参数的类时
<T extends A & B & C>
,类型删除流程将使用类型T
替换A
。
但是如果T
类型的变量调用接口B
或C
中的方法声明,Java会用它做什么?
当我们创建一个泛型类型的实例,例如ArrayList<String>
时,类型参数String
也将被删除,但调用get
方法将返回类型
String
,此信息来自哪里,因为它已被删除?
我知道使用java反射,但我需要一些具体的解释。
答案 0 :(得分:6)
不使用反射 - 投射是。例如,请使用以下代码:
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
的字节码(删除了构造函数):
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
}
在b()
和c()
的每次checkcast
来电之前,请注意invokeinterface
条说明。
结果就像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<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
是什么 - 该信息已被删除。
答案 1 :(得分:2)
在编译器完成类型检查后,类型擦除有效地用Object
替换所有泛型类型参数。在<T extends A & B & C>
示例中,A
就像其他所有内容一样被删除。
当您在get
上调用ArrayList<String>
时,编译器会生成字节码,将返回的对象自动转换为字符串。列表本身并不“知道”它是一个字符串列表(由于类型擦除),但是调用get
的代码知道它希望从列表中获取的东西是一个字符串。