为什么泛型使用了 extends 就不能存东西了?

2021-07-18 15:03:44 +08:00
 Mexion

小白学习泛型产生了个问题。为什么泛型中使用了 extends 来约束类型就不能存东西了?

比如<? extends Animal>这表名 Animal 和 Animal 的子类型,子类型比如 Dog,Cat 按理来说不都是应该可以存入的吗,都按照 Animal 来存,取出来也都认为是 Animal 类型不行吗?

求各位大哥们解解疑惑

4114 次点击
所在节点    问与答
34 条回复
namelosw
2021-07-18 15:10:56 +08:00
你的问题描述的不是很清楚,可以写一点代码片段来描述一下问题出在哪。

---

不过直觉上听起来是型变问题,也就是 List<Dog> 并不一定是 List<Animal> 的子类。

如果是协变,也就是 Animal 和 Dog 只读的,那么 List<Dog> 就是 List<Animal> 的子类。

如果反过来逆变,也就是只写的,那么 List<Animal> 是 List<Dog> 的子类。

如果又可读又可写,那就是不变,两种类型之间没有子类关系。
ipwx
2021-07-18 15:11:59 +08:00
我猜你这是 Java 。。。 老哥你该写明一下语言吧。

Java 不熟,不过你要不试试显式转换成 Animal ?
ReferenceE
2021-07-18 15:28:10 +08:00
你这是啥语言?没看懂
JasonLaw
2021-07-18 15:30:13 +08:00
Jooooooooo
2021-07-18 15:31:23 +08:00
这个确实是初学让人非常迷惑的地方

可以了解 PECS 原则, extends 只能是生产者, 往外提供东西, 只能 get, 无法往里 put

同理 super 只能是消费者, 往里放东西, 只能 put, 无法往 get

至于为什么, 可以考虑 Collection<? extends Fruit> 这样一个集合你知道肯定都是水果, 但不知道具体是哪一种, 所以往里面放会破坏原有的结构(原来如果是苹果你往里放一个香蕉肯定不行), 唯一知道的这里面都是水果, 所以往外 get 是可以的

super 是类似的.


想要知道的更多可以仔细读下这个:

泛型: www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html

协变: dzone.com/articles/covariance-and-contravariance
Mexion
2021-07-18 15:41:21 +08:00
@Jooooooooo 这就是我疑惑的地方,既然已经约束了是水果,那么取出来的肯定都是水果,那么我往里存香蕉那也是一个水果,往里存苹果也是一个水果,不是应该保证存进去的都是水果就行了吗,为什么直接就不能存了,存进去保证是水果和水果的子类型,取出来自然也是水果啊
Mexion
2021-07-18 15:42:41 +08:00
@namelosw 我的疑惑点就是为什么协变是只读的
Mexion
2021-07-18 15:43:06 +08:00
@ReferenceE 是 Java
Mexion
2021-07-18 15:44:07 +08:00
@ipwx 对,是 Java,是所有东西都存不了,包括 Animal
Jooooooooo
2021-07-18 15:51:10 +08:00
@Mexion 因为可以这么写 List<? extends Number> = new ArrayList<Integer>()

这样往里放一个 double 肯定不合法
Jooooooooo
2021-07-18 15:52:38 +08:00
@Mexion 要明白的一点是 ? extends 并不是表明一个范围, 而是精准了描述了一个东西. 而 ? 导致无法事先知道是什么东西, 所以也无法往里放.
Mexion
2021-07-18 15:58:07 +08:00
@Jooooooooo 看到这句 List<? extends Number> = new ArrayList<Integer>(),貌似懂了,谢谢老哥解惑。
JinTianYi456
2021-07-18 15:58:16 +08:00
@Mexion #6 是不是这样?(我没验证)
1. 有一个方法 void do(Collection<? extends Fruit> f)
2. 然后变量 a 是 Collection<苹果>, 调用 do 方法,do 方法内可以执行 f.add(香蕉)吗?
3. 同理变量 b 是 Collection<香蕉>, 调用 do 方法,do 方法内可以执行 f.add(苹果)吗?
JasonLaw
2021-07-18 15:58:23 +08:00
@JasonLaw #4 List<? extends Animal> list 可以是 List<Dog>,也可以是 List<Cat>,你不能将一个 Cat 放进 List<Dog>中,但是你可以确定从 list 取出来的东西一定是一个 Animal 。想了解更多,建议你认真看一下 4 楼那个链接。
Mexion
2021-07-18 16:13:29 +08:00
@JasonLaw 懂了,谢谢老哥
JasonLaw
2021-07-18 17:00:49 +08:00
@Jooooooooo #11 我不太理解你说的这段话。

1. 怎么理解“? extends 并不是表明一个范围,而是精准了描述了一个东西”?
2. 怎么理解“? 导致无法事先知道是什么东西, 所以也无法往里放”?
Jooooooooo
2021-07-18 17:31:36 +08:00
@JasonLaw List<> 里面装的东西是个明确的东西, 无论是 Integer 还是 Long, 无法往一个 List<Integer> 里面放 Long, 反过来也不行. ? extends Number 使得 List 里面的东西变得不可知, 只知道里面肯定都是 Number.

因为不知道里面是不是装着 Integer, 所以无法放 Integer, 其它类型同理.

还是那个例子最清晰, 可以这么 List<? extends Number> = new ArrayList<Integer>() 不可以往里面放 Long 是不是很明显.
JasonLaw
2021-07-18 18:06:21 +08:00
@Jooooooooo #17

你说“因为不知道里面是不是装着 Integer, 所以无法放 Integer”,其实是因为不知道是 List<Integer>还是 List<Long>,所以才不能放 Integer 。如果是“因为不知道里面是不是装着 Integer, 所以无法放 Integer”,那么如果有个 List<Number> list 的话,我们也不知道里面是不是装着 Integer,但是我们是可以放 Integer 的。

如果有错误的话,麻烦指出。
Jooooooooo
2021-07-18 18:26:05 +08:00
@JasonLaw 后面的这个可以搜下 协变. 我理解这是语言带来的特性.

举个例子, 假设有一个父类 Person, 有一个子类 Child extends Person.

有一个方法, 入参和返回值都是 Person. public Person test(Person p)

你会发现只有入参可以传 Child, 返回值不能用 Child 去接. 这个应该是语言的取舍, 我记得在 Java 5 之前入参也不能是 Child, 后来改的.
Jooooooooo
2021-07-18 18:30:44 +08:00
@JasonLaw 稍微再想了一下, 为什么 List<Number> 可以装 Integer 而 ? extends Number 不行. 原因就应该是类似你说的更准确, 因为 ? extends Number 可以是 Integer 也可以是 Long, 但并不明确是哪个, 所以哪个都不能装.

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/790199

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX