泛型
- 泛型类
- 泛型接口
- 泛型方法
关于泛型的几点:
- 尖括号里的每个元素都代表一种未知类型
- 尖括号只能出现在类名之后(作用于类的泛型)或者方法返回值之前(方法泛型)
使用泛型的好处:
- 类型安全 避免粗心导致的类转换异常
- 提升代码可读性 编码阶段即可知道对象类型
- 提升了代码的复用率
泛型类
class Map<K>{
// 修饰成员变量
private K key;
// 修饰参数
public Map(K key){}
// 修饰返回值
public K get(){
// 修饰局部变量
K key1 = key;
return key1;
}
}
泛型方法
// <T> 声明的是这个方法的泛型参数 后面的T声明的是方法的返回类型
public static <T> T run(T obj){
return obj;
}
泛型限定
// 约定T必须是Comparable的子类
<T extends Comparable>
// 可同时指定多个父接口
<T extends Comparable&Serializable>
通配符
// 只能接受S的自身或子类
<? extends S>
// 能接收S自身及其超类
<? super S>
// 不限制类型,只能使用object接收
<?>
PESC原则
Producer Extends Consumer Super
- 上界<? extends T> 当只想从集合中获取元素,请把这个集合看成生产者
List<Apple> apples = new ArrayList<>();
apples.add(new Apple());
List<? extends Fruit> basket = apples;//按上一个例子,这个是可行的
for (Fruit fruit : basket)
{
System.out.println(fruit);
}
//basket.add(new Apple()); //编译错误
//basket.add(new Fruit()); //编译错误
- 下界<? super T> 当你仅仅想增加元素到集合,把这个集合看成消费者
List<Apple> apples = new ArrayList<>();
apples.add(new Apple());
List<? super Apple> basket = apples;//这里使用了super
basket.add(new Apple());
basket.add(new RedApple());
//basket.add(new Fruit()); //编译错误
Object object = basket.get(0);//正确
//Fruit fruit =basket.get(0);//编译错误
//Apple apple = basket.get(0);//编译错误
//RedApple redApple = basket.get(0);//编译错误
出现这个原则的原因是因为 List<Apple>
跟 List<Fruit>
没有任何关系
如Java API中对集合的复制:
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
...
}
泛型擦除
- 虚拟机中没有泛型,只有普通类和方法
- 在编译阶段,泛型参数被擦除为限定类型,并进行相关类型转换
- 虚拟机也会合成桥方法来保持方法多态
补救:
如果想要在运行时获取泛型的类型 那就必须通过某种手段记录泛型的 Class 对象