Java泛型(类型擦除)


前言

    在了解Java泛型的使用后对于泛型背后原理的理解也很重要,类型擦除是泛型实现的重要知识点,理解类型擦除后将会使你对整个Java泛型有更清楚的认识。本篇文章重点介绍Java泛型的类型擦除,对于Java泛型不了解的可参考其他文章。

一、Java泛型

二、类型擦除

通过前面两篇文章我们了解到Java泛型在很大程度上是Java语言中的东西而不是虚拟机中的,这是因为在Java程序编译期间会将泛型信息擦除而转变为非泛型类,例如List<String>List<Integer> 在编译后擦除了泛型类型只留下了原始类,因此Java虚拟机看到的都只是List

@Test
    public void test() {
        List<String> a = new ArrayList<String>();
        List<Integer> b = new ArrayList<Integer>();
        a.add("hello");
        b.add(12);
        System.out.println(a.getClass() == b.getClass());
    }
-----------------------输出-----------------------
true

1.1 无限制类型擦除
当在类的定义时没有进行任何限制,那么在类型擦除后将会被替换成Object,例如<T>、<?>都会被替换成Object
1.2 有限制类型擦除
当类定义中的参数类型存在上下限(上下界),那么在类型擦除后就会被替换成类型参数所定义的上界或者下界,例如<? extend Person>会被替换成Person,而<? super Person>则会被替换成Object

小结:通过上面对类型擦除的介绍后就不难理解为什么上限通配符适合读操作,下限通配符适合写操作了。

2.1 类型擦除与多态

/* 父类 */
public class Person<T> {
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}
/* 子类 */
public class Man extends Person<String> {
    @Override
    public String getValue() {
        return super.getValue();
    }

    @Override
    public void setValue(String value) {
        super.setValue(value);
    }
}

在了解过泛型的类型擦除后,我们知道泛型只存在于编译期间即Java中的泛型是 ‘伪泛型’,那么观察上面的例程我们期望Man类继承Person类后重写其getValue()setValue()方法实现多态,但实际是在类型擦除后父类PersonT会被替换为Object而子类Man对应方法的参数类型为String子类并不是重写了父类方法(参数类型不同一个是Object一个是String),这样看来泛型的类型擦除和多态之间有冲突,那么Java是如何解决这个问题的?

我们通过反编译Man类程序得到下面内容,通过观察我们发现在类型擦除后Java虚拟机为我们生成了四个方法。其中getValue()setValue()都有一个Object类型和一个String类型的方法。

注:反编译命令 javap -c [Class名称] 就可以编译class文件。

Compiled from "Man.java"
public class generic.Man extends generic.Person<java.lang.String> {
  public generic.Man();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method generic/Person."<init>":()V
       4: return

  public java.lang.String getValue();
    Code:
       0: aload_0
       1: invokespecial #2                  // Method generic/Person.getValue:()Ljava/lang/Object;
       4: checkcast     #3                  // class java/lang/String
       7: areturn

  public void setValue(java.lang.String);
    Code:
       0: aload_0
       1: aload_1
       2: invokespecial #4                  // Method generic/Person.setValue:(Ljava/lang/Object;)V
       5: return

  public void setValue(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #3                  // class java/lang/String
       5: invokevirtual #5                  // Method setValue:(Ljava/lang/String;)V
       8: return

  public java.lang.Object getValue();
    Code:
       0: aload_0
       1: invokevirtual #6                  // Method getValue:()Ljava/lang/String;
       4: areturn
}

Java巧妙的使用了桥接的设计模式,在类型擦除后通过生成两个参数都为Object的桥方法来覆盖父类,因此我们在调用子类方法时实际上是调用了我们看不到的两个桥接方法,从而解决了类型擦除和多态的冲突。

2.2 泛型的特性所引起的问题

-通过对类型擦除的了解,我们在回过头来看使用泛型的一些限制条件就很清晰明朗了。

  1. 泛型类型变量不能是基本数据类型
    因为当类型擦除后会变为Object等引用类型,因此基本数据类型不能用于定义泛型类型变量。

  2. 使用instanceof注意点
    instanceof 是用来测试一个对象是否为一个类的实例,而对于泛型在类型擦除后会变为Object等引用类型,因此instanceof检测和类型转换工作只对原始类型进行。

注:原始类型为擦除去了泛型信息后的真正类型,任何一个泛型泛型其相应的原始类型都会被自动提供,类型擦除擦除后使用其限定类型(无限定时用Object)替换。

  1. 泛型在静态方法和静态类中的问题
    泛型类中的静态方法和静态变量不能使用泛型类所声明的泛型类型参数,因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用,因此对象没有创建也就无法确定这个泛型参数是何种类型。另外由于类型擦除的原因,实际上多个泛型方法只存在一个对应的原始类,因此静态变量在这多个泛型类实例之间是共享的。
public class Test2<T> {    
    public static T one;   //编译错误    
    public static  T show(T one){ //编译错误    
        return null;    
    }    
}
/* 这是一个泛型方法,在泛型方法中使用的T是自己在方法中定义的 T,而不是泛型类中的T。*/
public class Test2<T> {    
    public static <T>T show(T one){ //这是正确的    
        return null;    
    }    
}
  1. 不能创建一个泛型类型的实例
T obj = new T(); 	// 不被允许的

应为其中的T可能是Object或者其他限界,因此对其 new没有意义.

  1. 没有泛型数组
    由于数组是协变的加上泛型最终会被类型擦除,因此泛型数组违背了泛型的设计初衷是被禁止的。

总结

Java泛型实际上是“伪泛型”,它只是在编译期存在当程序到运行时则会被Java虚拟机进行类型擦除,同时Java虚拟机通过桥接的方式将编译期和运行期的程序(泛型类和原始类)连接了起来,从而实现了泛型的整个过程。另外对于Java泛型的许多限制都可以通过类型擦除和泛型的设计初衷来解释(将可能出现的运行时异常移至编译其解决)。

end

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值