在推出泛型之前,程序员通过构建元素类型为Object的集合,存储多个任意类型的数据对象;在使用该对象的过程中,程序员需要明确知道存储每个元素的数据类型(几乎不可能),否则会引发ClassCastException。
显然,类型的优点:
在不知道集合存储类型的前提下,泛型能够统一集合的元素类型——类型安全
,消除通过Object集合带来的强制类型转换
。
泛型的本质就是参数化类型
,将所操作的数据类型指定为一个参数,实现动态更改。
定义语法
class 类名<泛型标识,泛型标识,...>{
private 泛型标识 变量名;
}
常用的泛型标识:
? 表示不确定的java类型
T (type) 表示具体的一个java类型
K V (key value) 分别代表java键值中的Key Value
E (element) 代表Element
如果泛型类在new的时候没有指定泛型类型,将默认使用Object来操作。
泛型类型不支持基本类型。
同一个泛型类,不同的泛型参数,本质上还是同一类型=>泛型类本身
// intGeneric是<Integer>,strGeneric是<String>
System.out.println(intGeneric.getClass()==strGenenric.getClass()) // true
// intGeneric.getClass() ==>Generic
因此可知
指定泛型类型,不支持多态
指定泛型类型,不支持重载
如果使用泛型通配符,支持重载和多态
从泛型类派生子类
子类也是泛型类,则子类泛型标识至少要包含父类的泛型标识
子类不是泛型类,则父类的泛型类型需要显式声明 此时子类使用父类的方法,方法的返回值都会变成明确的类型
实现类的实现规则跟派生子类的规则一致。
定义格式
修饰符 <T,E,...> 返回值类型 方法名(形参){}
实例: 传入一个T类型的集合,随机返回一个T
public <T> T test(List<T> list){
return list.get(random.nextInt(list.size()));
}
泛型类中的泛型方法的泛型标识符优先级高于泛型类 如果泛型类的泛型标识为T,泛型类中的泛型方法的标识也为T,当我们new出泛型类,指定泛型为Integer之后,再调用泛型方法,并指定泛型为String,依旧是可以的。
泛型类中使用的泛型类的成员方法,不能作为静态方法,但是泛型方法可以。
可变参数
public <E> void print(E... es){
for(E e:es){
sout(e);
}
}
类型通配符一般是?
,代替具体的类型实参
==>类型通配符是实参,不是形参。
类型通配符可以实现重载和多态。
类型通配上限 指定通配上限为Number,此时通配时可以使用Number及其子类
public static void test(Box<? extends Number> boxs){
Integer box = boxs.getFirst();
}
类型通配下限 指定通配必须是该类型及其父类。
public static void test(Box<? super Number> boxs){
//Integer box = boxs.getFirst();
Object box = boxs.getFirst();
}
泛型只作用于编译阶段,编译后泛型会被擦除,因此泛型能够与jdk1.5之前的版本兼容。
无限制类型擦除
如果仅仅是<T>,则统统擦除为Object
有限制类型擦除
如果存在上限,则擦除为上限
桥接方法
父类会按无限制擦除规则擦除,子类会生成一个桥接方法。
可以声明带泛型的数组引用,但不能直接创建带泛型的数组对象
可以通过java.lang.reflect.Array的newInstance(Class<T>, int)创建T[]数组