关于Java 泛型 ?extends T的问题,搞不清楚

时间:2020-11-26 16:22:20
在学习java泛型时 ,看到了泛型的一个例子

List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;
fruits.add(new Strawberry());

其中,Fruit是父类、Apple和Strawberry是子类,add操作执行错误,给出的解释是

“这个? extends T 通配符告诉编译器我们在处理一个类型T的子类型,但我们不知道这个子类型究竟是什么。因为没法确定,为了保证类型安全,我们就不允许往里面加入任何这种类型的数据。”

我不明白编译器怎么会不知道这个子类型是什么呢?如果不知道的话,语句List<? extends Fruit> fruits = apples;为什么不报错呢?

19 个解决方案

#1


这道题应该是编译时类型和运行时类型的区别

当是成员变量的时候,他一直保持的是编译时类型
而当是成员方法时,他保持的是运行时类型


List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;    //这个fruits = apples保持的是编译时类型时类型 ,把List<Apple>赋值给 List<? extends Fruit>当然没问题 
fruits.add(new Strawberry());     //这个fruits.add()保持的是运行时类型,fruits运行时他的类型是List<Apple>当然运行出错了,但是在编译时就错误了,更别提运行时了,编译时编译器不会把List的泛型去掉,他会跟着List<? extends Fruit>去判断,所以你编译都过不了,当编译成class文件后他就会自动把泛型去掉,任何同类型的集合的字节码是一个,你可以通过反射给他注入你行要得值

#2


List<? extends Fruit> fruits = apples; //fruits 是apples的父类,当然没问题了(在编译时就是这样判断的)

#3


感觉还是不是非常清楚......,不过还是要谢谢jueshengtianya

#4


哪里有? extends T ?楼主贴全了吗?还是仅仅就运行了贴出来的三句就报那个错了?

#5


这个完全是正确的!
1、首先 List<Apple> 是  List<? extends Fruit> 的子类型,一个父类型的变量fruits 当然能指向一个子类型的变量引用apples;

2、“这个? extends T 通配符告诉编译器我们在处理一个类型T的子类型,但我们不知道这个子类型究竟是什么。” 
这是正确的,有上面第一点可以知道List<Strawberry>也是List<? extends Fruit>的子类型,你也说了,泛型能保证类型安全,而 fruits变量的静态类型是List,有个? extends Fruit的类型参数,编译器能确定的就是这个变量的类型是List,其能存储的对象类型是Fruit的某种字类型或Fruit

所以这里fruits.add(new Strawberry());是不能操作的!

#6


哦看了下一楼的,这个提示感觉很不精准啊,如果有原文更好,如果原因是strawberry无法被加入只容纳Apple的容器,它的解释是无法确定子类型,strawberry类型应该是很明显的了才对

#7


List<? extends Fruit>   这个的意思是:这个泛型必须是Fruit类型或者是它的子类,起到一个约束范围的作用,如果new出来的对象泛型不是这个范围内的类型,就出报错的
   
  如果你不写的话,它默认是 List<? entends Object>

#8


3ks lxbccsu、dracularking、n5233873,我是不是能这么理解:

List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;
这两句确定了fruits这个list此时只能存储Apple了,不可以再存储其他子类了

#9


应该限制上限的超类

#10


引用 8 楼  的回复:
3ks lxbccsu、dracularking、n5233873,我是不是能这么理解:

List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;
这两句确定了fruits这个list此时只能存储Apple了,不可以再存储其他子类了

No. 
List<? extends Fruit> fruits = apples;只表示fruits这个变量可以引用apples;
也即:
List<Strawberry> strawberrys = new ArrayList<Strawberry>();
List<? extends Fruit> fruits = strawberrys;
也是可以的;

其实编译器根据 ? extends Fruit 只知道:list中可以是Fruit或它的任何某种子类型对象,但具体是什么类型是不确定的,所以你add(new Fruit()); add(new Strawberry()); add(new Apple()); 编译器都是会报错的;

如果难理解,反过来理解:
比如fruits.add(new Strawberry()),这里是add一个Strawberry类型对象,而fruits要求add的对象类型必须是Fruit或它的任何某种子类型对象,这个 某种类型能用Strawberry或Fruit或Apple来表示吗

#11


暂1楼和10楼,最赞一楼,理解的这么透彻。

#12


List<? extends Fruit> fruits = apples;
我认为这里的fruits表示的一个存储fruit子类的集合,而apples里面存贮的就是fruit的子类apple,所以没有错误

#13


引用 8 楼  的回复:
3ks lxbccsu、dracularking、n5233873,我是不是能这么理解:

List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;
这两句确定了fruits这个list此时只能存储Apple了,不可以再存储其他子类了

我觉得现在剩下来的一个主要问题就是为什么在加元素时以子类型不确定的理由而编译错误(这个可以理解,? extends Fruit就表示某一种unknown type,范围是包括Fruit及其子类),在apples赋给fruits时就没有以同样的理由报错,我想是设计上的原因,类型范围意义上它们是可以这样排列的:
List<? extends Fruit>   
大于
List<Apple> apples
大于
new ArrayList<Apple>

方向是祖先类=>子孙类

但是在涉及容器是否可添加元素判断时,因编译时容器元素类型只是根据? extends Fruit来确定,因此会报不确定子类型,比如即使改成这样也会编译报错:
List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;
fruits.add(new Apple()); //compile error

这个10楼说过了

但我们知道实际上这个List要装的就是Apple,但在编译时就不知道。


#14


更正一下上面的类型排列:
List<? extends Fruit>

super than

List<Apple>

super than

ArrayList<Apple>

#15


该回复于2012-05-30 13:22:36被版主删除

#16


运行肯定错的,这个多态总知道的,至于能不能通过编译,可以尝试下,不行,就想办法解释就是了,或者,原理很透彻,本来就懂,跟本不用去试.

#17



List<Apple> apples = new ArrayList<Apple>();
// 将apples 的引用给 fruits 没问题 , 因为apples的定义为List<Apple> , 而Apple是Fruit的子类
List<? extends Fruit> fruits = apples;
//经过上一条代码,fruits 就等于 fruits = new ArrayList<Apple>(); 只能往fruits 里面放Apple//对象了
//但是这里放Strawberry对象,所以就报错了。
fruits.add(new Strawberry());

#18


List<? extends Fruit> fruits
?表示类型未知,为防止运行时类型转换出错,有限制的通配符列表不接受除了null之外的任何其他值

#19


小弟初学,尚未遇到过这么深的问题,个人觉得范型只是可以确定更小的范围,因此钻石不考虑!

#1


这道题应该是编译时类型和运行时类型的区别

当是成员变量的时候,他一直保持的是编译时类型
而当是成员方法时,他保持的是运行时类型


List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;    //这个fruits = apples保持的是编译时类型时类型 ,把List<Apple>赋值给 List<? extends Fruit>当然没问题 
fruits.add(new Strawberry());     //这个fruits.add()保持的是运行时类型,fruits运行时他的类型是List<Apple>当然运行出错了,但是在编译时就错误了,更别提运行时了,编译时编译器不会把List的泛型去掉,他会跟着List<? extends Fruit>去判断,所以你编译都过不了,当编译成class文件后他就会自动把泛型去掉,任何同类型的集合的字节码是一个,你可以通过反射给他注入你行要得值

#2


List<? extends Fruit> fruits = apples; //fruits 是apples的父类,当然没问题了(在编译时就是这样判断的)

#3


感觉还是不是非常清楚......,不过还是要谢谢jueshengtianya

#4


哪里有? extends T ?楼主贴全了吗?还是仅仅就运行了贴出来的三句就报那个错了?

#5


这个完全是正确的!
1、首先 List<Apple> 是  List<? extends Fruit> 的子类型,一个父类型的变量fruits 当然能指向一个子类型的变量引用apples;

2、“这个? extends T 通配符告诉编译器我们在处理一个类型T的子类型,但我们不知道这个子类型究竟是什么。” 
这是正确的,有上面第一点可以知道List<Strawberry>也是List<? extends Fruit>的子类型,你也说了,泛型能保证类型安全,而 fruits变量的静态类型是List,有个? extends Fruit的类型参数,编译器能确定的就是这个变量的类型是List,其能存储的对象类型是Fruit的某种字类型或Fruit

所以这里fruits.add(new Strawberry());是不能操作的!

#6


哦看了下一楼的,这个提示感觉很不精准啊,如果有原文更好,如果原因是strawberry无法被加入只容纳Apple的容器,它的解释是无法确定子类型,strawberry类型应该是很明显的了才对

#7


List<? extends Fruit>   这个的意思是:这个泛型必须是Fruit类型或者是它的子类,起到一个约束范围的作用,如果new出来的对象泛型不是这个范围内的类型,就出报错的
   
  如果你不写的话,它默认是 List<? entends Object>

#8


3ks lxbccsu、dracularking、n5233873,我是不是能这么理解:

List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;
这两句确定了fruits这个list此时只能存储Apple了,不可以再存储其他子类了

#9


应该限制上限的超类

#10


引用 8 楼  的回复:
3ks lxbccsu、dracularking、n5233873,我是不是能这么理解:

List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;
这两句确定了fruits这个list此时只能存储Apple了,不可以再存储其他子类了

No. 
List<? extends Fruit> fruits = apples;只表示fruits这个变量可以引用apples;
也即:
List<Strawberry> strawberrys = new ArrayList<Strawberry>();
List<? extends Fruit> fruits = strawberrys;
也是可以的;

其实编译器根据 ? extends Fruit 只知道:list中可以是Fruit或它的任何某种子类型对象,但具体是什么类型是不确定的,所以你add(new Fruit()); add(new Strawberry()); add(new Apple()); 编译器都是会报错的;

如果难理解,反过来理解:
比如fruits.add(new Strawberry()),这里是add一个Strawberry类型对象,而fruits要求add的对象类型必须是Fruit或它的任何某种子类型对象,这个 某种类型能用Strawberry或Fruit或Apple来表示吗

#11


暂1楼和10楼,最赞一楼,理解的这么透彻。

#12


List<? extends Fruit> fruits = apples;
我认为这里的fruits表示的一个存储fruit子类的集合,而apples里面存贮的就是fruit的子类apple,所以没有错误

#13


引用 8 楼  的回复:
3ks lxbccsu、dracularking、n5233873,我是不是能这么理解:

List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;
这两句确定了fruits这个list此时只能存储Apple了,不可以再存储其他子类了

我觉得现在剩下来的一个主要问题就是为什么在加元素时以子类型不确定的理由而编译错误(这个可以理解,? extends Fruit就表示某一种unknown type,范围是包括Fruit及其子类),在apples赋给fruits时就没有以同样的理由报错,我想是设计上的原因,类型范围意义上它们是可以这样排列的:
List<? extends Fruit>   
大于
List<Apple> apples
大于
new ArrayList<Apple>

方向是祖先类=>子孙类

但是在涉及容器是否可添加元素判断时,因编译时容器元素类型只是根据? extends Fruit来确定,因此会报不确定子类型,比如即使改成这样也会编译报错:
List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;
fruits.add(new Apple()); //compile error

这个10楼说过了

但我们知道实际上这个List要装的就是Apple,但在编译时就不知道。


#14


更正一下上面的类型排列:
List<? extends Fruit>

super than

List<Apple>

super than

ArrayList<Apple>

#15


该回复于2012-05-30 13:22:36被版主删除

#16


运行肯定错的,这个多态总知道的,至于能不能通过编译,可以尝试下,不行,就想办法解释就是了,或者,原理很透彻,本来就懂,跟本不用去试.

#17



List<Apple> apples = new ArrayList<Apple>();
// 将apples 的引用给 fruits 没问题 , 因为apples的定义为List<Apple> , 而Apple是Fruit的子类
List<? extends Fruit> fruits = apples;
//经过上一条代码,fruits 就等于 fruits = new ArrayList<Apple>(); 只能往fruits 里面放Apple//对象了
//但是这里放Strawberry对象,所以就报错了。
fruits.add(new Strawberry());

#18


List<? extends Fruit> fruits
?表示类型未知,为防止运行时类型转换出错,有限制的通配符列表不接受除了null之外的任何其他值

#19


小弟初学,尚未遇到过这么深的问题,个人觉得范型只是可以确定更小的范围,因此钻石不考虑!

#20