Java–泛型
泛型类型擦除
Java中的泛型是伪泛型,在Java编译期间,所有的泛型信息会被擦除。
生成的字节码中不包含泛型类型信息,在使用泛型时添加的类型参数在编译时都会去掉,这个过程就是类型擦除。
而且泛型是在JDK 5 之后引入,类型擦除同时也是为了向下兼容。
先定义泛型类,查看擦除后的字节码。
Java代码:
/**
* 盘子,要接受哪种水果
* @param <T> 需要接受的水果
*/
public class Plates<T> {
private T t;
public T getT() {
return t;
}
public void cut(T t){
//...
}
}
Java字节码:
// class version 52.0 (52)
// access flags 0x21
// signature <T:Ljava/lang/Object;>Ljava/lang/Object;
// declaration: com/minlukj/demo/Plates<T>
public class com/minlukj/demo/Plates {
// compiled from: Plates.java
// access flags 0x1
public <init>()V
// access flags 0x1
// signature ()TT;
// declaration: T getT()
public getT()Ljava/lang/Object;
// access flags 0x1
// signature (TT;)V
// declaration: void cut(T)
public cut(Ljava/lang/Object;)V
}
可以看到字节码的类名没有泛型,成员变量T变成了Object。
cut方法接受的参数也变成了Object。
所以从这里可以看出,泛型擦除后会变成原始类Object。
如果限定泛型类型之后会将Object强转为限定类型。
public class Plates<T extends Fruit> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
字节码
// class version 52.0 (52)
// access flags 0x21
// signature <T:Lcom/minlukj/demo/Fruit;>Ljava/lang/Object;
// declaration: com/minlukj/demo/Plates<T extends com.minlukj.demo.Fruit>
public class com/minlukj/demo/Plates {
// compiled from: Plates.java
// access flags 0x2
// signature TT;
// declaration: t extends T
private Lcom/minlukj/demo/Fruit; t
// access flags 0x1
// signature ()TT;
// declaration: T getT()
public getT()Lcom/minlukj/demo/Fruit;
// access flags 0x1
// signature (TT;)V
// declaration: void setT(T)
public setT(Lcom/minlukj/demo/Fruit;)V
}
从上面代码可以看出,泛型从Object变成了限定类型Fruit。
再来看看多限定类型下的字节码。
public class Plates<T extends Fruit & IFruit> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
多类型限定使用 “&” 符号进行连接,这里的多类型只能一个父类,其他的都是接口,因为Java只能单继承。
// class version 52.0 (52)
// access flags 0x21
// signature <T:Lcom/minlukj/demo/Fruit;:Lcom/minlukj/demo/IFruit;>Ljava/lang/Object;
// declaration: com/minlukj/demo/Plates<T extends com.minlukj.demo.Fruit extends com.minlukj.demo.IFruit>
public class com/minlukj/demo/Plates {
// compiled from: Plates.java
// access flags 0x2
// signature TT;
// declaration: t extends T
private Lcom/minlukj/demo/Fruit; t
// access flags 0x1
// signature ()TT;
// declaration: T getT()
public getT()Lcom/minlukj/demo/Fruit;
// access flags 0x1
// signature (TT;)V
// declaration: void setT(T)
public setT(Lcom/minlukj/demo/Fruit;)V
}
这里没有什么区别,编译器会在传入参数时进行判断,如果你传入的参数不符合限定类型就会报错。
从上面可以看出,编译器先检查传入的泛型类型,如果类型符合会进行类型擦除,再进行编译。
如果我们规定的泛型和传入的泛型不同,在编译的时候会提示无法转换。
泛型在静态方法和静态变量问题
public class Test<T>{
public static T t;
public static T get(T t){
return null;
}
}
如果使用static修饰泛型变量和泛型方法,编译器会监测出错误。
发生错误的原因
因为我们静态变量和静态方法不需要通过对象就可以调用,既然我们对象都没有创建,这个泛型的类型自然就无法定义。
在静态的泛型方法中定义泛型类型的话是可以使用的
private static <T> T get(T t){
return null;
}
在static后面定义泛型类型 ,这样的静态泛型方法才是正确的,而静态变量就不可以了。
总结
泛型在JDK 5 之后引入,为了满足向下兼容,Java的泛型是伪泛型。
伪泛型就是在编译器中会显示泛型,但在字节码中会将泛型转化成Object类型,如果泛型有限制,那么Object会转成这个限制类型。(T extends AAA), Object = AAA
如果继承泛型方法,且方法名一样,那么会生成一个桥方法。该方法用 synthetic bridge 修饰。
他接收的参数是Object类型
在内部会把Object强转为前一个set接收的类型,并且会调用这个set方法
关键字:CHECKCAST (强转)、INVOKEVIRTUAL(调用动态方法)