集合泛型
读看上界,写看下界
类型构造器

f表示类型构造器。一个类型被类型构造器处理后就是一个崭新的类型。
协变:
f(Dog)是f(Animal)的子类逆变:
f(Animal)是f(Dog)的子类不变:
f(Aniaml)与f(Dog)没有任何关系
类型构造器可以是
- 集合
java
List<Animal>与List<Dog>- 数组
java
Animal [] 与 Dog[]- 方法
java
say(Aniaml)与say(Dog)数组是协变的
在Java中数组是允许协变的,这是因为Java1时还没有泛型。如果没有协变,我们需要对每一种类型的数组编写处理函数:
java
void handleDog(Dog [] list){}
void handleCat(Cat [] list){}
void handleTom(Tom [] list){}开发者希望能够对数组进行通用处理。因此就设计了数组是允许协变的。
java
//三种类型的数组可以集成于一个数组中进行处理
void handleAnimal(Animal [] list){}但是这种设计是存在缺陷的:
java
handleAnimal(new Dog[10]);java
void handleAnimal(Animal [] list){
list[0]=new Dog();
list[1]=new Cat();//编译允许,但运行时会报错ArrayStoreException
}允许数组协变是Java的设计缺陷
如果你想保证类型安全,避免运行时异常,可以使用 泛型 + List 代替数组
泛型默认是不变的
泛型模式是不变性的:
java
List<Animal> animals=new ArrayList<Dog>();//协变,编译错误
List<Dog> dogs=new ArrayList<Animal>();//逆变,编译错误原因:
java
List<Animal> animals=new ArrayList<Dog>();//如果允许协变
animals.add(new Dog());
animals.add(new Cat());//类型不安全让泛型协变
为什么要让泛型支持协变
希望可以像数组一样:
java
void handleDog(List<Dog> list){}
void handleCat(List<Cat> list){}
void handleTom(List<Tom> list){}java
void handleAnimal(List<Animal> list){}如何支持协变
在类型参数前添加? extends即可让泛型支持协变
java
void handleAnimal(List<? extends Aniaml> list)此时以下调用都是合法的:
java
handleAnimal(new ArrayList<Dog> list);
handleAnimal(new ArrayList<Cat> list);
handleAnimal(new ArrayList<Tom> list);根据数组的设计可以知道协变存在类型安全问题,因此泛型协变是只读的
java
void handleAnimal(List<? extends Animal> list){
list.add(null);
Animal a=list.get(0);//只读
list.add(new Dog());//写入,编译错误
list.add(new Cat());//写入,编译错误
}Collection的设计原则
java
public interface Collection<T>{
boolean add(E e);
remove(Object o);
}add 设置成只支持类型E添加是确保类型安全
remove设置成Object是确保方法更具通用性
java
List<String> list = new ArrayList<>();
Object obj = "a";
list.remove(obj); //也应该允许,因为Object也有equals方法。让泛型逆变
在类型参数前添加? super即可让泛型支持逆变
java
void handleDog(List<? super Dog> list){}此时以下调用是合法的:
java
handleDog(new ArrayList<Dog> list);
handleDog(new ArrayList<Animal> list);java
void handleDog(List<? super Dog> list){
Object obj=list.get(0);//无法读取
list.add(new Dog());//写入允许,多态
list.add(new Tom());//写入允许,多态
list.add(new Animal());//写入,编译错误
}PECS
这里的生产者和消费者指的是集合本身
Producer Extends (生产者使用 extends): 如果你只需要从集合中读取数据(集合是数据的生产者)。
Consumer Super (消费者使用 super): 如果你只需要往集合中写入数据(集合是数据的消费者)。

| 类型 | 写法 | 能读 | 能写 |
|---|---|---|---|
| 无界 | <?> | Object | ❌ |
| 上界 | <? extends T> | T | ❌ |
| 下界 | <? super T> | Object | ✅ |
java
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (T item : src) { // src是生产者,从中读取T(或子类)对象
dest.add(item); // dest是消费者,向其中写入T(或父类)对象
}
}理解
List<Dog>
根据多态,List允许添加Dog,Teddy
List<? extends Dog>
通配符的作用是修饰整个复合类型,表示这个复合类型可以协变
协变是只读的,集合泛型是Dog
java
Dog dog =list.get(0)List<? super Dog>
通配符的作用是修饰整个复合类型,表示这个复合类型可以逆变
逆变是只写的,集合泛型为Dog,根据多态:
java
list.add(new Dog());//写入允许,多态
list.add(new Tom());//写入允许,多态