27.Android架构-泛型擦除机制
什么是泛型擦除
Java的泛型是JDK5新引入的特性,为了向下兼容,虚拟机其实是不支持泛型,所以Java实现的是一种
伪泛型机制,也就是说Java在编译期擦除了所有的泛型信息,这样Java就不需要产生新的类型到字节码,
所有的泛型类型最终都是一种原始类型,在Java运行时根本就不存在泛型信息。
定义一个泛型接口
interface Plate<R>{
void set(R r);
R get();
}
使用ASM ByteCode Viewer查看他的字节码
批注 2020-08-30 101425.png
可以看到我们设置的泛型R,被擦除为Object了,这就是泛型擦除
// class version 52.0 (52)
// access flags 0x600
// signature <R:Ljava/lang/Object;>Ljava/lang/Object;
// declaration: Plate<R>
abstract interface Plate {
// compiled from: TestFanxing.java
// access flags 0x401
// signature (TR;)V
// declaration: void set(R)
public abstract set(Ljava/lang/Object;)V
// access flags 0x401
// signature ()TR;
// declaration: R get()
public abstract get()Ljava/lang/Object;
}
定义一个Plate1实现Plate接口
class Plate1<R> implements Plate<R>{
private R r;
@Override
public void set(R r) {
this.r = r;
}
@Override
public R get() {
return r;
}
}
他的bytecode为
class Plate1 implements Plate {
private Ljava/lang/Object; r
<init>()V
......
public set(Ljava/lang/Object;)V
......
public get()Ljava/lang/Object;
......
}
定义一个指定泛型类型的Plate2实现Plate接口
class Plate2<R extends Comparable<R>> implements Plate<R>{
private R r;
@Override
public void set(R r) {
this.r = r;
}
@Override
public R get() {
return r;
}
}
可以看到我们限定了泛型的类型,那么他的bytecode是什么样的?
class Plate2 implements Plate {
private Ljava/lang/Comparable; r
<init>()V
......
public set(Ljava/lang/Comparable;)V
......
public get()Ljava/lang/Comparable;
......
public synthetic bridge get()Ljava/lang/Object;
......
public synthetic bridge set(Ljava/lang/Object;)V
L0
LINENUMBER 43 L0
ALOAD 0
ALOAD 1
CHECKCAST java/lang/Comparable
INVOKEVIRTUAL Plate2.set (Ljava/lang/Comparable;)V
RETURN
L1
LOCALVARIABLE this LPlate2; L0 L1 0
// signature LPlate2<TR;>;
// declaration: this extends Plate2<R>
MAXSTACK = 2
MAXLOCALS = 2
}
可以看到虽然我们在Plate2中只定义了一个set get方法,但是bytecode中却有两个,其中一个get set方法添加了synthetic bridge 表示这是一个桥接方法,作用是为了保持多态性,可以看到CHECKCAST java/lang/Comparable
,检查类型是否为Comparable,如果是的话再去调用上边的public set(Ljava/lang/Comparable;)V
方法。可以这样理解,set(Ljava/lang/Object;)V
是从Plate接口实现来的,set(Ljava/lang/Comparable;)V
是他本身的,因为限定了类型范围
Java编译器具体是如何擦除泛型的
- 检查泛型类型,获取目标类型
- 擦除类型变量,并替换为限定类型
如果泛型类型的类型变量没有限定(<T>),则用Object作为原始类型
如果有限定(<T extends XClass>),则用XClass作为原始类型
如果有多个限定(T extends XClass1&XClass2),则使用第一个边界XClass1作为原始类 - 在必要时插入类型转换以保持类型安全
- 生成桥方法以在扩展时保持多态性
泛型擦除的残留
上边我们是通过showbytecode的方式查看的字节码,但是如果你点开类生成的class文件,你会发现,泛型既然被擦除了为什么在class中仍然可以看到?其实这里看到的只是签名而已,还保留了定义的格式,这样对分析字节码有好处。你甚至可以通过javap -c Plate2.class反编译class,你会发现,R还是能被看到,我们要看bytecode,通过showbytecode的方式比较真实
图片.png
泛型既然被擦除了,为什么通过反射还可以拿到泛型的类型?其实在类的常量池中保存了泛型信息
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
public class TestFanxing {
Map<String, String> map;
//擦除 其实在类常量池里面保留了泛型信息
public static void main(String[] args) throws Exception {
Field f = TestFanxing.class.getDeclaredField("map");
/**
* getType() 和 getGenericType()的区别 :
* 1.首先是返回的类型不一样,一个是Class对象一个是Type接口。
* 2.如果属性是一个泛型,从getType()只能得到这个属性的接口类型。但从getGenericType()还能得到这个泛型的参数类型。
* 3.getGenericType()如果当前属性有签名属性类型就返回,否则就返回 Field.getType()。
*/
System.out.println("f.getGenericType() "+f.getGenericType()); // java.util.Map<java.lang.String, java.lang.String>
System.out.println("f.getType() "+f.getType()); // java.util.Map<java.lang.String, java.lang.String>
//ParameterizedType是Type的子接口,表示一个有参数的类型,也就是包含泛型
System.out.println(f.getGenericType() instanceof ParameterizedType); // true
ParameterizedType pType = (ParameterizedType) f.getGenericType();
//getRawType(): 返回承载该泛型信息的对象, 如上面那个Map<String, String>承载范型信息的对象是Map
System.out.println("pType.getRawType() "+pType.getRawType()); // interface java.util.Map
for (Type type : pType.getActualTypeArguments()) {
// getActualTypeArguments(): 返回实际泛型类型列表, 如上面那个Map<String, String>实际范型列表中有两个元素, 都是String
System.out.println("pType.getActualTypeArguments() "+type); // 打印两遍: class java.lang.String
}
//Type getOwnerType(): 返回是谁的member.(上面那两个最常用)
System.out.println("pType.getOwnerType() "+pType.getOwnerType()); // null
}
}
使用泛型以及泛型擦除带来的副作用
1. 泛型类型变量不能使用基本数据类型
比如没有ArrayList<int>,只有ArrayList<Integer>.当类型擦除后,ArrayList的原始类中的类型变量(T)替换成Object,但Object类型不能 存放int值
图片.png
2. 不能使用instanceof 运算符
因为擦除后,ArrayList<String>只剩下原始类型,泛型信息String不存在了,所有没法使用instanceof
图片.png
3. 泛型在静态方法和静态类中的问题
因为泛型类中的泛型参数的实例化是在定义泛型类型对象 (比如ArrayList<Integer>)的时候指定的,而静态成员是不需要使用对象来调用的,所有对象都没创建,如何确定这个泛型参数是什么
图片.png
4. 泛型类型中的方法冲突
因为擦除后两个equals方法变成一样的了
图片.png
5. 没法创建泛型实例
因为类型不确定
图片.png6. 没有泛型数组
因为数组是协变(在某些情况下,即使某个对象不是数组的基类型,我们也可以把它赋值给数组元素。这种属性叫做协变(covariance)
),擦除后就没法满足数组协变的原则