泛型,继承和子类型,PECS

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中有很多用)

猜你喜欢

转载自blog.csdn.net/qq_40270270/article/details/112916962