JVM基础 -- 伪泛型

本文将从JVM字节码的角度解释一下Java为什么是伪泛型

伪泛型

  1. Java是伪泛型的,泛型在Java中只是语法糖,是通过类型擦除强制转换来实现的
  2. 运行期ArrayList<Integer>ArrayList<String>就是同一个类,在Java中并不存在类似与ArrayList<Integer>这种泛型类型

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class FakeGeneric {

static class Father {
}

static class Son extends Father {
}

private static void checkFather(Father father) {
}

public static void main(String[] args) {
Map<Father, Father> map = new HashMap<>(); // Map<K,V> , HashMap<K,V>
Father father = new Father();
Father son = new Son();

map.put(father, son); // V put(K key, V value);
checkFather(map.get(father)); // V get(Object key);
}
}

字节码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=4, args_size=1
0: new #2 // class java/util/HashMap
3: dup
4: invokespecial #3 // Method java/util/HashMap."<init>":()V
7: astore_1
8: new #4 // class me/zhongmingmao/test/FakeGeneric$Father
11: dup
12: invokespecial #5 // Method me/zhongmingmao/test/FakeGeneric$Father."<init>":()V
15: astore_2
16: new #6 // class me/zhongmingmao/test/FakeGeneric$Son
19: dup
20: invokespecial #7 // Method me/zhongmingmao/test/FakeGeneric$Son."<init>":()V
23: astore_3
24: aload_1
25: aload_2
26: aload_3
27: invokeinterface #8,3 // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
32: pop
33: aload_1
34: aload_2
35: invokeinterface #9,2 // InterfaceMethod java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;
40: checkcast #4 // class me/zhongmingmao/test/FakeGeneric$Father
43: invokestatic #10 // Method checkFather:(Lme/zhongmingmao/test/FakeGeneric$Father;)V
46: return
LocalVariableTable:
Start Length Slot Name Signature
0 47 0 args [Ljava/lang/String;
8 39 1 map Ljava/util/Map;
16 31 2 father Lme/zhongmingmao/test/FakeGeneric$Father;
24 23 3 son Lme/zhongmingmao/test/FakeGeneric$Father;
LocalVariableTypeTable:
Start Length Slot Name Signature
8 39 1 map Ljava/util/Map<Lme/zhongmingmao/test/FakeGeneric$Father;Lme/zhongmingmao/test/FakeGeneric$Father;>;
}

new HashMap<>()

在Java代码中,创建map时是带有泛型信息的

1
Map<Father, Father> map = new HashMap<>();

对应的JVM字节码

1
2
3
4
0: new           #2 // class java/util/HashMap
3: dup
4: invokespecial #3 // Method java/util/HashMap."<init>":()V
7: astore_1

在map的创建过程中并没有任何泛型信息,已经可以初步判断,Java并不存在HashMap<Father,Father>这样的泛型类型,是伪泛型
关于那4个指令的具体含义,可参照博文「字节码 - 方法重载 + 方法重写」,这里不再赘述

map.put(father, son)

在Java代码中,Map接口的put方法签名也是带有泛型信息的

1
V put(K key, V value);

对应的JVM字节码

1
27: invokeinterface #8,3    // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

put操作中也是没有任何泛型信息,只是简单地处理为java.lang.Object,这也是我们常说的类型擦除

map.get(father)

在Java代码中,Map接口的get方法签名也是带有泛型信息的

1
V get(Object key);

对应的JVM字节码

1
35: invokeinterface #9,2    // InterfaceMethod java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;

get操作中也是没有任何泛型信息,只是简单地处理为java.lang.Object,与put操作类似

checkFather(Father father)

Java代码

1
checkFather(map.get(father));

对应的字节码(排除get操作的字节码,前面已经提及过)

1
2
40: checkcast     #4        // class me/zhongmingmao/test/FakeGeneric$Father
43: invokestatic #10 // Method checkFather:(Lme/zhongmingmao/test/FakeGeneric$Father;)V
  1. 非常值得注意的是在调用checkFather方法之前有一个checkcast指令,这就是将之前的get操作得到的对象进行强制转换,由Object转换成Father
  2. 那么JVM在执行checkcast的时候怎么知道要转换成什么类型呢?前面分析字节码操作都是没有附带任何泛型信息的
  3. 答案就是在字节码文件中的LocalVariableTypeTable属性,因为Java是伪泛型的,因此需要这么一个属性详细记录的泛型信息,才能进行强制转换

伴随的问题

Java的伪泛型会带来一些语法上的尴尬,例如重载

1
2
3
4
5
public static void method(List<String> list){
}

public static void method(List<Integer> list){
}
  1. List<String> listList<Integer> listJava语法层面不同的参数类型,应该属于重载
  2. List<String> listList<Integer> listJVM层面,会进行所谓的类型擦除(其实只是伪泛型的一层伪装),是同一参数类型,方法具有相同的特征签名signature
0%