探究泛型类的本质
public class Test {
public static void main(String[] args) {
scene01();
}
public static void scene01() {
ArrayList<Apple> apples = new ArrayList<>();
ArrayList<Banana> bananas = new ArrayList<>();
// result = true
System.out.println(apples.getClass() == bananas.getClass());
}
}
上述代码运行结果为true,Apple和Banana类型不同为什么会判断相同呢?再看一个类的代码:
public class PlateImpl<T> implements Plate<T> {
private List<T> items = new ArrayList<T>(10);
public PlateImpl(){
}
@Override
public void set(T t) {
items.add(t);
}
@Override
public T get() {
int index = items.size() - 1;
if(index >= 0){
return items.get(index);
}else{
return null;
}
}
}
其中set函数的字节码为:
public set(Ljava/lang/Object;)V
L0
LINENUMBER 14 L0
ALOAD 0
GETFIELD generic/PlateImpl.items : Ljava/util/List;
ALOAD 1
INVOKEINTERFACE java/util/List.add (Ljava/lang/Object;)Z (itf)
POP
L1
LINENUMBER 15 L1
RETURN
L2
LOCALVARIABLE this Lgeneric/PlateImpl; L0 L2 0
// signature Lgeneric/PlateImpl<TT;>;
// declaration: this extends generic.PlateImpl<T>
LOCALVARIABLE t Ljava/lang/Object; L0 L2 1
// signature TT;
// declaration: t extends T
MAXSTACK = 2
MAXLOCALS = 2
set函数T变成了Object,说明泛型T被擦除成Object了。
如果泛型类型的类型变量有限定那么擦除后也会有所不同:
public class Plate2<T extends Comparable<T>> implements Plate<T> {
private List<T> items = new ArrayList<T>(10);
public Plate2(){
}
@Override
public void set(T t) {
items.add(t);
Collections.sort(items);
}
@Override
public T get() {
int index = items.size() - 1;
if(index >= 0){
return items.get(index);
}else{
return null;
}
}
}
set函数的字节码:
public set(Ljava/lang/Comparable;)V
L0
LINENUMBER 15 L0
ALOAD 0
GETFIELD generic/Plate2.items : Ljava/util/List;
ALOAD 1
INVOKEINTERFACE java/util/List.add (Ljava/lang/Object;)Z (itf)
POP
L1
LINENUMBER 16 L1
ALOAD 0
GETFIELD generic/Plate2.items : Ljava/util/List;
INVOKESTATIC java/util/Collections.sort (Ljava/util/List;)V
L2
LINENUMBER 17 L2
RETURN
L3
LOCALVARIABLE this Lgeneric/Plate2; L0 L3 0
// signature Lgeneric/Plate2<TT;>;
// declaration: this extends generic.Plate2<T>
LOCALVARIABLE t Ljava/lang/Comparable; L0 L3 1
// signature TT;
// declaration: t extends T
MAXSTACK = 2
MAXLOCALS = 2
set函数被擦除成Comparable类型,并不是Object,有限制的话会被擦除成第一个限制的类型,没有限制的话就擦除成Object。
有限制的情况下还会自动再生成一个set方法,是为了保持继承的多态性,因为Plate接口中的泛型会被擦除成Object,所以在Plate2中的set也要有一个Object的set(自动生成的桥方法)。
public synthetic bridge set(Ljava/lang/Object;)V
L0
LINENUMBER 7 L0
ALOAD 0
ALOAD 1
CHECKCAST java/lang/Comparable
INVOKEVIRTUAL generic/Plate2.set (Ljava/lang/Comparable;)V
RETURN
L1
LOCALVARIABLE this Lgeneric/Plate2; L0 L1 0
// signature Lgeneric/Plate2<TT;>;
// declaration: this extends generic.Plate2<T>
MAXSTACK = 2
MAXLOCALS = 2
public abstract interface generic/Plate {
// compiled from: Plate.java
// access flags 0x401
// signature (TT;)V
// declaration: void set(T)
public abstract set(Ljava/lang/Object;)V
// access flags 0x401
// signature ()TT;
// declaration: T get()
public abstract get()Ljava/lang/Object;
}
此外,泛型的擦除是有残留的,存在类的常量池当中。
总结Java编译器具体是如何擦除泛型的
-
检查泛型类型,获取目标类型
-
擦除类型变量,并替换为限定类型
如果泛型类型的类型变量没有限定(T),则用Object作为原始类型
如果有限定(T extends XClass),则用XClass作为原始类型
如果有多个限定(T extends XClass & XClass2)则使用第一个边界XClass作为原始类 -
在必要时插入类型转换以保持类型安全
-
生成桥方法以在扩展时保持多态性