岁暮阴阳催短景,天涯霜雪霁寒宵。——唐代王之涣《登鹳雀楼》
当我们通过get方法获取泛型是Integer的List中的第一个元素时,返回值会被自动转换为Integer类型, Java的自动类型转换的过程, 为什么泛型擦除了之后还能获取的到原来的类型
首先,我们要了解一下Java泛型和类型擦除的概念。泛型是Java中一种泛化类型的机制,它允许我们在类、接口和方法中定义类型参数,使得代码可以更灵活地复用。在编译期间,泛型信息会被擦除,即类型参数会被替换为其边界类型(如果有边界类型)或Object类型。类型擦除主要是为了保持向后兼容性,因为在Java泛型引入之前,已经存在的代码都是使用原始类型的。
现在我们来看一下,当我们通过get
方法获取泛型为Integer
的List
中的第一个元素时,为什么返回值会被自动转换为Integer
类型。
假设我们有一个泛型为Integer
的List
,代码如下:
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);
当我们调用get
方法获取第一个元素时,这里的过程分为两个阶段:编译时和运行时。
get
方法的返回类型变为Object
。编译器会在编译期间检查类型安全,以确保intList
只包含Integer
类型的元素。此外,编译器还会在需要的地方插入类型转换代码。在这个例子中,我们获取到的元素的类型是Object
,但是编译器知道它实际上是一个Integer
,所以会在适当的地方插入一个类型转换,将Object
类型转换为Integer
类型。
Integer firstElement = (Integer) intList.get(0);
get
方法返回的是一个Object
类型的对象。但由于编译器在编译期间已经插入了类型转换代码,所以这个Object
类型的对象会被自动转换为Integer
类型。总结一下,虽然泛型信息在编译时被擦除,但编译器会在编译期间检查类型安全,并在需要的地方插入类型转换代码。这样,在运行时,虽然泛型信息已经被擦除,但仍然可以获取到原来的类型,因为类型转换是在编译期间自动插入的。
验证一下:
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);
Integer firstElement = intList.get(0);
System.out.println("firstElement: " + firstElement);
}
}
反编译:
javap -c -p -v Main.class
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello world!
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: new #5 // class java/util/ArrayList
11: dup
12: invokespecial #6 // Method java/util/ArrayList."":()V
15: astore_1
16: aload_1
17: iconst_1
18: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
21: invokeinterface #8, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
26: pop
27: aload_1
28: iconst_2
29: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
32: invokeinterface #8, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
37: pop
38: aload_1
39: iconst_3
40: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
43: invokeinterface #8, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
48: pop
49: aload_1
50: iconst_0
51: invokeinterface #9, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
56: checkcast #10 // class java/lang/Integer
59: astore_2
60: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
63: new #11 // class java/lang/StringBuilder
66: dup
67: invokespecial #12 // Method java/lang/StringBuilder."":()V
70: ldc #13 // String firstElement:
72: invokevirtual #14 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
75: aload_2
76: invokevirtual #15 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
79: invokevirtual #16 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
82: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
85: return
我们看到这两行
51: invokeinterface #9, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
56: checkcast #10 // class java/lang/Integer
获取其实是一个object, 编译器自动帮我们推测了类型, 并且自动帮我们加上了强制类型转换