┌────────────────┐
│ 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");
}
}
┌───────────┌────┐
│ Gen │ T │
└───────────└────┘
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;
}
}
所谓上下转型是从右往左的
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 │
└────────────────┘
刚刚上面的都是引子,接下来进入正题
我们假设所有的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 =
,这是一种上转型
所以这个方法调用一定正确
继续看下面的方法
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);
}
实质就是
,这不就是下转型吗?我们了解到,直接下转型是不能运行的,是会报错的,那么自然这个方法是不可以实现的。
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
类型。」
// 前情回顾
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;
}
}
gen的泛型是 super PGrandSon>
,gen.getValue()
获得的类型也是 super PGrandSon>
,即获得的类型可能是PGrandSon
,也可能是PSon
,也可能是PFather
,当然也有PGrandpa
的可能性,搞不好就又出现下转型了,但是直到运行时刻,没谁能知道传入的到底是啥类型,如果出现下转型,则会报错,那么直接规定这种写法是错误的。
得出第二个结论, 「当参数泛型是 super 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 │
└────────────────┘
▲
│
......
又出现了下转型的情况,而这种情况依然无法在编译时直接发现,那么这种情况就直接不让你通过编译就行了。因为如果任由你这么写,报错的时候也很难找到错误。
得出第四个结论, 「当参数泛型是 extends SomeClass>
的时候,无法写入。」
我们继续分析, extends PGrandPa>
可以将继承树的树顶限制住,最高就一定是PGrandPa
,那么gen.getValue返回的任何类型要不是PGrandPa
,要不就是PGrandPa
的子类,PGrandPa grandPa = gen.getValue()
一定不会出现下转型的情况
我们得出第三个结论, 「当参数泛型是 extends SomeClass>
的时候,可以读出SomeClass
。」
「当参数泛型是 super SomeClass>
的时候,只能写入SomeClass
类型。」
「当参数泛型是 super SomeClass>
的时候,无法读出。」
「当参数泛型是 extends SomeClass>
的时候,无法写入。」
「当参数泛型是 extends SomeClass>
的时候,只能读出SomeClass
。」