1.协变
Apple extends Fruit
Apple[] 父类是 Fruit[] (数组的协变)
给定两种更具体的类型A和B(例如Fruit和Apple)
无论A和B是否相关
MyClass<A> 与MyClass<B> 都没半毛钱关系
他们的公共父对象是Object
只要T不变以下继承关系就存在
B extends A
C extends B
D exstends C
那么B<Apple> 继承自A<Apple>
那么C<Apple> 继承自B<Apple>
那么D<Apple> 继承自C<Apple>
2. <? extends Fruit >
Apple extends Fruit
Banana extends Fruit
RApple extends Apple
GApple extends Apple
Plate<? extends Fruit>
上界通配符覆盖的区域,表达的意思是一个能放水果以及一切是水果派生类的盘子Plate<? extends Fruit>是Plate<Fruit>以及Plate<Apple>的基类,直接的好处就是我们可以用苹果盘子给水果盘子赋值了
转成<? extends Fruit >后的后遗症:
不能放也不能拿,拿的时候只能用水果和Object(能读不能写)
set不能放,但可以通过反射放入,反射就破坏了泛型,安全没法保证
3. <? super Fruit >
Fruit extends Food
Plate<? super Fruit>
下界通配符覆盖区域,表示意思就是一个能放水果以及一切水果基类的盘子,Plate<? super Fruit>是Plate<Fruit>的基类,但不是Plate<Apple>的基类
转成<? super Fruit >后的后遗症:
可以把Plate<Fruit>以及它的基类Plate<Food>转换成Plate<? super Fruit>它可以存数据(能写不能读),
但是取出来后泛型信息丢失了,只能用Object存放
public static void scene05(){
Plate<? super Fruit> lowerfruitPlate = new Plate2<Food>();
lowerfruitPlate.set(new Apple());
lowerfruitPlate.set(new Banana());
//Fruit newFruit = lowerfruitPlate.get(); //错误
//Apple newFruit2 = lowerfruitPlate.get();//错误
Object newFruit3 = lowerfruitPlate.get();//ok
}
4. Plate<?>
非限定通配符,告诉这个是一个泛型,但是泛型信息类型是未知的,等价于Plate<? extends Object>
以下两个统称为限定通配符:
Plate<? extends T> //上界
Plate<? super T> //下界
通配符让泛型转型更灵活,泛型T一定是具体的某一种泛型类型,泛型类型之间转换会有问题(水果盘子里装香蕉和苹果的问题)。
非限定通配符的副作用: 既不能读也不能写
作用:保证检查类型
List 不会安全检查
List<?> 进行安全检查
5. Java泛型PECS原则
PECS 即Producer extends Consumer super,提升了API的灵活性
如果你只需要从集合中获得类型T,使用<? extends T>通配符
如果你只需要将类型T放到集合中,使用<? super T>通配符
如果你既要获取又要放置元素,则不使用任何通配符。例如List<Apple>
<?>既不能存也不能取
例子(copy方法的演化):
1.两个苹果盘子放苹果把id为1的copy到id为2的苹果上
public static void scene07() {
List<Apple> src = new ArrayList<>();
src.add(new Apple(1));
List<Apple> dest = new ArrayList<>(10);
dest.add(new Apple(2));
System.out.println(dest);
copy(dest,src);
System.out.println(dest);
}
public static void copy(List<Apple> dest, List<Apple> src){
Collections.copy(dest,src);
}
结果:
[Apple[id=2]]
[Apple[id=1]]
2.如果改成对两个香蕉盘子放香蕉实现copy方法那么就不能调用copy方法了,所以使用泛型改进copy方法为copy1。
public static void scene07() {
List<Banana> src1 = new ArrayList<>();
src1.add(new Banana(1));
List<Banana> dest1 = new ArrayList<>(10);
dest1.add(new Banana(2));
System.out.println(dest1);
copy1(dest1,src1);
System.out.println(dest1);
}
public static <T> void copy1(List<T> dest, List<T> src){
Collections.copy(dest,src);
}
结果:
[Banana[id=2]]
[Banana[id=1]]
3.如果改成对一个水果盘子放香蕉和一个香蕉盘子放香蕉实现copy方法那么就不能调用copy1了,所以使用改进copy1方法为copy2。
public static void scene07() {
List<Banana> src2 = new ArrayList<>();
src2.add(new Banana(1));
List<Fruit> dest2 = new ArrayList<>(10);
dest2.add(new Banana(2));
System.out.println(dest2);
Test.<Banana>copy2(dest2,src2);
System.out.println(dest2);
}
public static <T> void copy2(List<? super T> dest, List<T> src){
Collections.copy(dest,src);
}
结果:
[Banana[id=2]]
[Banana[id=1]]
4.Test.<Banana>copy2(dest2,src2);改成Test.<Fruit>copy2(dest2,src2);实现copy方法那么就不能调用copy2了,所以使用改进copy2方法为copy3。
public static void scene07() {
List<Banana> src2 = new ArrayList<>();
src2.add(new Banana(1));
List<Fruit> dest2 = new ArrayList<>(10);
dest2.add(new Banana(2));
System.out.println(dest2);
Test.<Fruit>copy3(dest2,src2);
System.out.println(dest2);
}
public static <T> void copy3(List<? super T> dest, List<? extends T> src){
Collections.copy(dest,src);
}
结果:
[Banana[id=2]]
[Banana[id=1]]
copy3也是最终版的copy
// An highlighted block
public static <T> void copy3(List<? super T> dest, List<? extends T> src){
Collections.copy(dest,src);
}
src是生产者,可以从里面取数据(读),只要是T的子类型就可以传参
dest是消费者,可以把里面写数据(写),只要是T的父类型也可以往里面存
使用通配符就一个目的,为了灵活转型(API中有很多用)