在类型擦除过程中,Java 编译器擦除所有类型参数,如果类型参数是有界的,则将每个类型参数替换为其第一个边界,如果类型参数是无界的,则将其替换为 Object。
但是当我们使用已编译的类引用方法时,编译器会确保在编译时进行类型检查。
为了。例如,如果我在 A 类上使用泛型编译它,然后通过 B 类引用它,在编译期间它将确保类型检查。
如果java在编译时擦除类型,那么编译后的类文件如何确保类型检查?
除了擦除的上限之外,运行时没有类型检查。Java 泛型是关于编译器检查的。
另一方面,也许您的问题只是关于编译器如何检查类型参数信息是否从字节码中消失。答案是它并没有从整个类文件中消失:它作为元数据附加,可供编译器(以及反射 API)使用,但与执行代码无关。
我不同意在运行时没有检查 - 但确实所有类型检查都是在编译时完成的。
查看生成的实际字节码总是很有趣。让我们来看看这段代码
import java.util.*;
class Types{
public static void main(String [] args){
List<String> list = new ArrayList<String>();
list.add("hello");
System.out.println(list.get(0));
}
}
编译它然后使用 反汇编它javap -c
,我们得到
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String hello
11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
16: pop
17: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
20: aload_1
21: iconst_0
22: invokeinterface #7, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
27: checkcast #8 // class java/lang/String
30: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
33: return
}
我们注意到两件事
checkcast
以确保从列表中检索到的对象实际上是 a String
。这是编译器插入的运行时检查。在泛型之前手动引入的东西。