Java泛型

 

声明

泛型方法

在方法返回类型之前添加类型参数声明;每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。

泛型类/接口

在类名/接口名后面添加了类型参数声明。

使用场景

  • 泛型方法定义了一个“对几种对象的相同操作“。相比在父类中声明方法的做法,有更明确的意义和更好的解耦性。比如ArrayList的add()。如果这样一个类的所有方法,“几种”的意义相同,那么可以把“几种”的意义上升到类,即泛型类;
  • 另一种泛型类是为了“具化一个类与另一个特定类的关系”。比如SpecialFragment。原本Fragment依附的是Activity,getParent()返回的是Activity;如果用泛型类SpecialFragment,重写getParent()等方法,就可以表达这个SpecialFragment是依附于SpecialActivity,不是其他Activity。
  • 还有一种泛型类是作为“一个类型不固定的成员变量的占位符”。比如处理网络请求的返回数据,可以把返回数据定义为三个成员变量code返回码、message结果信息、data具体数据;不同接口返回的data类型都不固定,data的类型就可以声明为泛型;在处理完code、message后,针对data的具体类型做不同处理。
  • 配合反射机制实现动态代理、适配者模式等。

实现

Java的泛型只与编译期有关。在生成的Java字节码中是不包含泛型的。

声明泛型的类型参数会在编译期的时候去掉。这个过程就称为“类型擦除”。“类型擦除”的同时,使用类型变量的地方会被其限定类型(无限定的变量用Object)替换。最终出现在字节码中的只包含限定类型的真正类型,被称为“原始类型”。

由于存在类型擦除,如何保证类型安全?解决方法是:在类型擦除前,先检查传入方法的参数的类型是否符合引用的类型变量,如果不符合,报编译错误。

由于存在类型擦除,如何实现重写?解决方法是:在编译时,为每个重写的泛型方法先创建一个桥方法,桥方法的类型参数被替换为限定类型,在桥方法中再调用重写的方法。

限制

  • 类型变量不能是基本数据类型;可以用包装类型。
  • 类型变量不能用于运行时类型检查;可以用通配符?。
  • 不能抛出也不能捕获泛型类的对象;不能在catch子句中使用类型变量。
  • 不能用类型变量声明数组。
  • 不能实例化泛型。
  • 不能用类型变量声明静态方法和静态变量。

类型参数没有继承的特性

ArrayList<Object> list = new ArrayList<String>();

上面这行代码是没有意义的。因为在添加元素时,对ArrayList进行类型检查,元素可以是Object类型的;对象的类型限制(String)没有意义。

ArrayList<String> list = new ArrayList<Object>();

上面这行代码是会报编译错误的。因为假如编译通过,在添加元素时,对象的类型限制是Object,假如添加一个Integer对象,在取元素时,必然发生Integer对象强转String对象的类型转换错误。

协变和逆变

协变:把窄类型对象的引用(Integer[])赋值给宽类型的声明(Number[])(宽 = 窄);若要使用类型参数,宽类型要以<? extends>表示。

逆变:把宽类型对象的引用(Number[])赋值给窄类型的声明(Integer[])(窄 = 宽);若要使用类型参数,窄类型要以<? super>表示。