声明
泛型方法
在方法返回类型之前添加类型参数声明;每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
泛型类/接口
在类名/接口名后面添加了类型参数声明。
使用场景
- 泛型方法定义了一个“对几种对象的相同操作“。相比在父类中声明方法的做法,有更明确的意义和更好的解耦性。比如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
ArrayList<String> list = new ArrayList<Object>();
上面这行代码是会报编译错误的。因为假如编译通过,在添加元素时,对象的类型限制是Object,假如添加一个Integer对象,在取元素时,必然发生Integer对象强转String对象的类型转换错误。
协变和逆变
协变:把窄类型对象的引用(Integer[])赋值给宽类型的声明(Number[])(宽 = 窄);若要使用类型参数,宽类型要以<? extends>表示。
逆变:把宽类型对象的引用(Number[])赋值给窄类型的声明(Integer[])(窄 = 宽);若要使用类型参数,窄类型要以<? super>表示。