黑马程序员技术交流社区

标题: 关于java泛型,关于 ? extends T 求帮助 [打印本页]

作者: 冯越    时间: 2012-5-17 15:39
标题: 关于java泛型,关于 ? extends T 求帮助
本帖最后由 冯越 于 2012-5-17 15:40 编辑

最近在学习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;为什么不报错呢?
作者: 李文富    时间: 2012-5-17 15:59
这题我是这样理解的:主要的问题应该在fruits.add(new Strawberry());,因为java编译器有严格的类型控制,
为了确保存储的是同一类型,举个例子:Manager 是继承自 Employee 如果定义list<Employee>存放 Manager 类型,那么一定会
出现难以预料的错误。这也是泛型的用意,确保存储类型的唯一性。存了apples类就只能继续存apple类。如果你说我一定的存,那么
用反射吧,这种限定只出现在编译时期,完成编译<>里面的内容就没有了,你可以利用反射获取add方法就可以往里面放你想放的!
作者: 徐然    时间: 2012-5-17 16:05
本帖最后由 徐然 于 2012-5-17 16:17 编辑

你是说Apple和Strawberry都是 Fruit的子类?
那就是问题了
apples的泛型定的是Apple,那?通配符表示的自然也就是Apple了
而add添加的是Strawberry,那是当然添加不了的
因为当你在让fruits指向apples的时候,fruits的泛型类型已经定下来了就是Apple
对于<? extends Fruit>这句话,就是说他不确定你要传入什么类型,只是给你规定了一个范围,一旦你给定他一个类型的话
那么他的泛型类型就确定下来,不可改变了
所以fruits不能添加Strawberry类型了

而编译器的意思就是说,对于fruits,我们只接收Apple类型,你正在操作的Strawberry不是Apple,所以不接收你添加的任何Strawberry类型数据
作者: 逄焕玮    时间: 2012-5-17 17:53
你说苹果和草莓都是Fruit的子类的,那么 List<? extends Fruit> 表明里面存储的是Fruit的子类,但具体是苹果还是草莓呢,就不知道了
泛型通配符 <? extends Fruit> 不确定具体要存储什么类型,只是告诉一个范围,即 Fruit的子类
但是一旦确定是草莓还是苹果以后,就不能再存储另一种了
作者: 袁冬梅    时间: 2012-5-17 18:11
逄焕玮 发表于 2012-5-17 17:53
你说苹果和草莓都是Fruit的子类的,那么 List

我也很疑惑,这么说是怎么确定它是苹果还是草莓呢?是从=号右面的 new ArrayList<这里面确定么?>


作者: 逄焕玮    时间: 2012-5-17 19:00
袁冬梅 发表于 2012-5-17 18:11
我也很疑惑,这么说是怎么确定它是苹果还是草莓呢?是从=号右面的 new ArrayList

...

嗯,是的
List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;

List<? extends Fruit> fruits = new ArrayList<Apple>();
将apples值赋给fruits ,则引用变量fruits指向了new ArrayList<Apple>()实例对象,从而确定了fruits的泛型类型
我是这么理解的,希望老师给个确切点儿的解释,谢谢:loveliness:
作者: 彩虹    时间: 2012-5-18 20:06
       张老师的视频里不是总结了:不能把问号给具体的类型,但可以把具体的类型给问号,使用?通配符可以引用其他各种参数化的类型
例如: String.class.asSubclass(Number.class);
          Class<?>y;
          Class<String>x;
          y=x;  //编译不报错
          x=y;  //编译报错
List<? extends Fruit> fruits = apples;可以这么理解:前面泛型定义的是继承Fruit类的?通配符类型,不确定子类型是什么,但既然它是继承Fruit类,就应该包括Fruit类的各种类型,Apples当然也包括在内,前面是能接收各种Fruit的类型,而后面只是Apples,所以当然能被接收。这一句,就相当于y=x;这句,所以编译时不报错。
作者: 冯越    时间: 2012-5-18 21:18
本帖最后由 冯越 于 2012-5-18 21:20 编辑
徐然 发表于 2012-5-17 16:05
你是说Apple和Strawberry都是 Fruit的子类?
那就是问题了
apples的泛型定的是Apple,那?通配符表示的自然 ...

很谢谢你的回答,不过你的回答是不对的。按你的回答 ruits.add(new Apple()); 应该是不会报错的,但事实是编译器依然报错了并且和 fruits.add(new Strawberry()); 的错误如出一辙。我这两天翻书查了一下资料,找到了问题的所在,就把他和你分享一下吧。
首先,我们来看看编译器为什么会报错,他报错是因为 fruits.add(new Strawberry()); 的add方法所添加的类型是不正确的。那为什么会不正确呢,他应该添加什么样的类型呢?List<? extends Fruit> fruits 说明了他允许添加的类型是 Fruit或是Fruit的子类的一种,那到底是哪一种呢? 编译器并不知道,所以他报错了。因为这样做保证不了容器类型的安全,而这正是泛型的职责所在。
如果这样不好理解的话,就反过来看问题。假设我们可以按上面的方式添加,于是我们 fruits.add(new Strawberry()); fruits.add(new Fruit()); fruits.add(new Apple()); 那现在我们的容器到底属于那种类型呢?Apple? Fruit?Strawberry?我们无法确定,编译器更没有办法。而且这样做我们更是无法准确的从容器中取出对象,这岂不是变成了没有泛型之前的摸样了,那要泛型还有什么用呢? 所以,泛型是绝对不允许我们这样做的。
作者: 徐然    时间: 2012-5-18 22:28
冯越 发表于 2012-5-18 21:18
很谢谢你的回答,不过你的回答是不对的。按你的回答 ruits.add(new Apple()); 应该是不会报错的,但事实是 ...

那如果是这样的话我就真糊涂了
那fruits.add()能添加什么类型呢
只能添加Fruit类型吗?那?通配符是什么情况下可以实现呢
作者: 冯越    时间: 2012-5-18 23:57
本帖最后由 冯越 于 2012-5-18 23:58 编辑
徐然 发表于 2012-5-18 22:28
那如果是这样的话我就真糊涂了
那fruits.add()能添加什么类型呢
只能添加Fruit类型吗?那?通配符是什么 ...

fruits.add()不能添加任何类型。那?通配符 还有什么用呢?  当然有用,而且还有很大的用处。之所以出现add方法报错是因为我们把 ? extends Fruit 用错了地方。请看这几句:
ArrayList<Apple> apples = new ArrayList<Apple>();
ArrayList<Sting> str = new ArrayList<String>();
List<? extends Fruit> fruits = apples;//这句编译器是不会报错了,父类引用指向子类对象并且泛型检查apples容器内类型也完全符合 <? extends Fruit>的限制,是Fruit的子类
List<? extends Fruit> fruits = str;//这句会报错,咋一看这也是父类引用指向子类对象,但这时泛型检查就起了作用,str容器内存放的String类型并不符合<? extends Fruit>,所以为了确保类型安全编译器报错了
<? extends Fruit>的作用就体现在这里
以上,都是我的个人理解!我想应该是这样的,如果你发现有什么不对的地方欢迎给我回复指正,我们一起再讨论。
作者: 傻子..    时间: 2012-8-22 15:51
那我有一个疑问,如果泛型将检测类型是否符合,那么下面如何解释:

List li=new ArrayList();
li.add("aaaaa");
List<Integer> list=li

上述代码执行通过,编译不报错。照理说第三句应该报错,为什么这里泛型不检测呢,说是li定义的时候没有指明泛型所以无法检测,那么也不应该让其通过编译。大家讨论下




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2