[toc]

泛型类型擦除

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(调用动态方法)

说点什么
支持Markdown语法
好耶,沙发还空着ヾ(≧▽≦*)o
Loading...