Java泛型
1. 什么是泛型
在推出泛型之前,程序员通过构建元素类型为Object的集合,存储多个任意类型的数据对象;在使用该对象的过程中,程序员需要明确知道存储每个元素的数据类型(几乎不可能),否则会引发ClassCastException。
显然,类型的优点:
在不知道集合存储类型的前提下,泛型能够统一集合的元素类型——类型安全
,消除通过Object集合带来的强制类型转换
。
泛型的本质就是参数化类型
,将所操作的数据类型指定为一个参数,实现动态更改。
2. 泛型类、接口
类
定义语法
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
因此可知
-
指定泛型类型,不支持多态
public static test(Box<Number> box){} main(){ // 合法 Box<Number> box1; // 尽管Integer instanceOf Number依旧不合法 // 因为本质上这都是指向同一个Box类 Box<Integer> box2; }
-
指定泛型类型,不支持重载
// 两个其中一个会报错 // 因为他们本质上都是指向同一个Box类,因此不满足重载 public static test(Box<Number> box){}; public static test(Box<Integer> box){}
如果使用泛型通配符,支持重载和多态
从泛型类派生子类
- 子类也是泛型类,则子类泛型标识至少要包含父类的泛型标识
class child<T,E,K> extends father<T>{}
- 子类不是泛型类,则父类的泛型类型需要显式声明
此时子类使用父类的方法,方法的返回值都会变成明确的类型class child extends father<String>{}
接口
实现类的实现规则跟派生子类的规则一致。
3. 泛型方法
定义格式
修饰符 <T,E,...> 返回值类型 方法名(形参){}
- 修饰符与返回值中间的<T>非常重要,可以理解为声明该方法为泛型方法
- 只有声明了<T>的方法才是泛型方法,泛型类中使用了泛型的成员方法不是泛型方法
- <T>相当于声明,该方法中要使用泛型T
实例:
传入一个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);
}
}
4. 类型通配符
类型通配符一般是?
,代替具体的类型实参
==>类型通配符是实参,不是形参。
类型通配符可以实现重载和多态。
类型通配上限
指定通配上限为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();
}
5. 泛型擦除
泛型只作用于编译阶段,编译后泛型会被擦除,因此泛型能够与jdk1.5之前的版本兼容。
无限制类型擦除
如果仅仅是<T>,则统统擦除为Object
有限制类型擦除
如果存在上限,则擦除为上限
桥接方法
父类会按无限制擦除规则擦除,子类会生成一个桥接方法。
6. 泛型和数组
- 可以声明带泛型的数组引用,但不能直接创建带泛型的数组对象
这样不被允许 List<String>[] listArr = new ArrayList<>[5]; 等号左边的声明式合法的,后面的创建语句非法
- 可以通过java.lang.reflect.Array的newInstance(Class<T>, int)创建T[]数组