泛型
- 泛型类
- 泛型接口
- 泛型方法
关于泛型的几点:
- 尖括号里的每个元素都代表一种未知类型
- 尖括号只能出现在类名之后(作用于类的泛型)或者方法返回值之前(方法泛型)
使用泛型的好处:
- 类型安全 避免粗心导致的类转换异常
- 提升代码可读性 编码阶段即可知道对象类型
- 提升了代码的复用率
泛型类
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>不能往里存,只能往外取,适合频繁往外面读取内容的场景。
下界<? super T>不影响往里存,但往外取只能放在Object对象里,适合经常往里面插入数据的场景
- 上界 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;//这里使用了superbasket.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 对象
类型变化关系
A、B是类型,f(·)表示类型转换,≤表示继承关系,如A≤B,表示A继承于B
- f(·)是协变(covariant)的,如果 A ≤ B, f(A) ≤ f(B)
- f(·)是逆变(contravarian)的,如果A ≤ B,有f(B) ≤ f(A)
- f(·)是不变(nvariant)的,当上述两种都不成立,即f(A)和f(B)没有关系
- f(·)是双变(bivariant)的,如果A ≤ B,有f(B) ≤ f(A) 和 f(A) ≤ f(B)同时成立