关于Java泛型的通配符的写入和读出的一点思考

关于Java泛型的通配符的写入和读出的一点思考

1.前期准备

 ┌────────────────┐
 │     PGrandpa   │
 └────────────────┘
         ▲
         │
 ┌────────────────┐
 │     PFather    │
 └────────────────┘
         ▲
         │        
 ┌────────────────┐
 │      PSon      │
 └────────────────┘
         ▲
         │
 ┌────────────────┐
 │    PGrandson   │
 └────────────────┘
 public class PGrandPa {//👴会种地

    public void doFarm() {//👴只会种地
        System.out.println("I can raise wheat in the farm.");
    }
}
public class PFather extends PGrandPa {//👨老爹会做生意
    public void doTrade() {//老爹会种地、做生意
        System.out.println("I can trade with others and make a lot of money!");
    }
}
public class PSon extends PFather {//🧑儿子读书上大学
    public void doStudy() {//儿子会种地、做生意、读书上大学
        System.out.println("I can reading many books and go to college.");
    }
}
public class PGrandSon extends PSon {//👶孙子是个骄傲的新时代青年,还在家写作业

    public void doHomeWork() {//孙子会种地、做生意、读书上大学、并且是个作业没写完的骄傲男孩
        System.out.println("I am a proud Chinese and I am doing my homework");
    }
}

 ┌───────────┌────┐    
 │       GenT │
 └───────────└────┘     
public class Gen<T> {
    public T value;
    public Gen(T value) {
        this.value = value;
    }
    public void setValue(T t) {
        this.value = t;
    }

    public T getValue() {
        return value;
    }
}                         

在这里插入图片描述
在这里插入图片描述

☕ 2. 关于上、下转型

所谓上下转型是从右往左的

Father father ← new Son(),上转型

Son son ← new Father(),下转型

public static void main(String[] args) {
    Gen<PGrandPa> genGrandPa = new Gen<>(new PGrandPa());
    Gen<PFather> genFather = new Gen<>(new PFather());
    Gen<PSon> genSon = new Gen<>(new PSon());
    Gen<PGrandSon> genGrandSon = new Gen<>(new PGrandSon());
    PGrandPa pGrandPa = new PGrandPa();
    PFather pFather = new PFather();
    PSon pSon = new PSon();
    PGrandSon pGrandSon = new PGrandSon();
    pFather = new PGrandPa();//❌报错!!!!🔺爹不能当爷爷🔺
    pFather = new PSon();// ✔爹可以是儿子,可以是孙子
}

我们先理解清楚刚刚的代码为什么对,为什么错

public static void main(String[] args) {
    PGrandPa pGrandPa = new PGrandPa();
    PFather pFather = new PFather();
    PSon pSon = new PSon();
    PGrandSon pGrandSon = new PGrandSon();
    pFather = new PGrandPa();
    pFather.doFarm();
    pFather.doTrade();//不可能,因为new出来的对象是👴,根本没有这个功能,强行调用也是不行
}

我们想强行让爷爷当爸爸是不太行的,不能强行下转型pFather = new PGrandPa();(在继承树中,爹的位置在👴的下面,把👴变成爹就相当于是下转型)

但是上转型是可以的pFather = new PSon();

 ┌────────────────┐
 │     PGrandpa   │
 └────────────────┘
     ▲      │下转型
     │上转型 ▼
 ┌────────────────┐
 │     PFather    │
 └────────────────┘
     ▲      │下转型
     │上转型 ▼ 
 ┌────────────────┐
 │      PSon      │
 └────────────────┘

刚刚上面的都是引子,接下来进入正题

☕ 3. <? super SomeClass>的写入

我们假设所有的addS和addE都是正确的,

public static void addS(Gen<? super PGrandSon> gen, PGrandSon p) {
    gen.setValue(p);
}

这个是绝对正确的,因为Gen<? super PGrandSon>中的泛型T一定是PGrandSon或者是其父类gen.setValue(p)实质就是

<? super PGrandSon>value = <PGrandSon>p;,这是一种上转型

所以这个方法调用一定正确

继续看下面的方法

public void main(String[] args) {
    Gen<PGrandSon> genGrandSon = new Gen<>(new PGrandSon());
    PSon pSon = new PSon();
    addS(genGrandSon, pSon);
}
public static void addS(Gen<? super PGrandSon> gen, PSon p) {
    gen.setValue(p);
}

实质就是<PGrandSon>value = <PSon>p;,这不就是下转型吗?我们了解到,直接下转型是不能运行的,是会报错的,那么自然这个方法是不可以实现的。

public static void addS(Gen<? super PGrandSon> gen, PGrandSon p) {//①
    gen.setValue(p);
}
public static void addS(Gen<? super PGrandSon> gen, PSon p) {//②
    gen.setValue(p);
}
public static void addS(Gen<? super PGrandSon> gen, PFather p) {//③
    gen.setValue(p);
}
public static void addS(Gen<? super PGrandSon> gen, PGrandPa p) {//④
    gen.setValue(p);
}

根据上面的分析我们知道了,②,③,④这三个方法都是错误的我们无法确定传入的gen的泛型和第二个参数p之间的上下关系,所以可能产生下转型的情况,所以报错。

值得注意的是,传入null是可以的,无论Gen的泛型是如何。

得出第一个结论,👉 「当参数泛型是<? super SomeClass>的时候,只能写入SomeClass类型。」

☕ 4. <? super SomeClass>的get

// 前情回顾
public class Gen<T> {
    public T value;
    public Gen(T value) {
        this.value = value;
    }
    public void setValue(T t) {
        this.value = t;
    }

    public T getValue() {
        return value;
    }
}   

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RmmJp56F-1642942937130)(安卓面试笔记.assets/image-20220123200627041.png)]

gen的泛型是<? super PGrandSon>gen.getValue()获得的类型也是<? super PGrandSon>,即获得的类型可能是PGrandSon,也可能是PSon,也可能是PFather,当然也有PGrandpa的可能性,搞不好就又出现下转型了,但是直到运行时刻,没谁能知道传入的到底是啥类型,如果出现下转型,则会报错,那么直接规定这种写法是错误的。

得出第二个结论,👉 「当参数泛型是<? super SomeClass>的时候,无法读出。」

☕ 5. <? extends SomeClass>的写入

在这里插入图片描述

通过上上节我们知道,? super PGrandSon起码还可以写入PGrandSon类型的实例,因为通过super关键字起码可以限定?的类型是在Object~PGrandSon这个范围内,所以只要第二个参数是PGrandSon类型,就一定不会产生下转型的情况,?代表的类型一定是PGrandSon或者比PGrandSon更高

? extends PGrandPa则没有边了,如果出现下图的情况

public static void main(String[] args) {
    addE(genGrandSon, pFather);
}
public static void addE(Gen<? extends PGrandPa> gen, PFather p) {
    gen.setValue(p);
}
 ┌────────────────┐
 │     PGrandpa   │
 └────────────────┘
         ▲
         │
 ┌────────────────┐
 │     PFather    │
 └────────────────┘
         ▲
         │        
 ┌────────────────┐
 │      PSon      │◄─── <PSon> p
 └────────────────┘
         ▲
         │
 ┌────────────────┐
 │    PGrandson   │◄─── Gen<PGrandSon> gen
 └────────────────┘
         ▲
         │
 ┌────────────────┐
 │   PPGrandson   │
 └────────────────┘
         ▲
         │
 ┌────────────────┐
 │   PPPGrandson  │
 └────────────────┘
         ▲
         │
       ......

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vg0Rqbqn-1642942937132)(安卓面试笔记.assets/image-20220123115727550.png)]

又出现了下转型的情况,而这种情况依然无法在编译时直接发现,那么这种情况就直接不让你通过编译就行了。因为如果任由你这么写,报错的时候也很难找到错误。

得出第四个结论,👉 「当参数泛型是<? extends SomeClass>的时候,无法写入。」

☕ 6. <? extends SomeClass>的get

在这里插入图片描述

我们继续分析,<? extends PGrandPa>可以将继承树的树顶限制住,最高就一定是PGrandPa,那么gen.getValue返回的任何类型要不是PGrandPa,要不就是PGrandPa的子类,PGrandPa grandPa = gen.getValue()一定不会出现下转型的情况

我们得出第三个结论,👉 「当参数泛型是<? extends SomeClass>的时候,可以读出SomeClass。」

总结

👉 「当参数泛型是<? super SomeClass>的时候,只能写入SomeClass类型。」

👉 「当参数泛型是<? super SomeClass>的时候,无法读出。」

👉 「当参数泛型是<? extends SomeClass>的时候,无法写入。」

👉 「当参数泛型是<? extends SomeClass>的时候,只能读出SomeClass。」

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值