class A{}
class B extends A{}
class GenericMethods {
public <T> T f1(T x) {
System.out.println(x.getClass().getName());
return x;
}
}
public class testP {
public static void main(String[] args) {
GenericMethods gm = new GenericMethods();
A a = gm.f1(new B());
//B b = gm.f1(new A());编译报错
}
}
f1 方法是一个泛型方法,返回值是< >
里的T
,形参也是< >
里的T
。所以二者推断出来的具体类型必须一样,或者符合多态。
执行A a = gm.f1(new B())
时,返回值处的T
被推断为A
,形参处的T
本来会被推断为B
,但是由于前者,形参处的T
这里被推断为A
。这里传B
对象作为实参符合多态,泛型和多态不冲突。
还有就是 B b = gm.f1(new A());编译通不过怎么解释比较好,可以认为这句是推断为B
返回的也是B
只是这里不允许向下转型吗?
1
hantsy 2019-09-07 16:39:03 +08:00
牛马不相及的两个东西。
|
2
Raymon111111 2019-09-07 16:42:55 +08:00
B 是一个 A
而 A 却不是一个 B 换个简单的说法, 有一个装水果的篮子可以装苹果, 但是有一个装苹果的篮子肯定不能装水果, 因为这个水果可能是香蕉. |
3
Bromine0x23 2019-09-07 16:44:00 +08:00
类型参数只能从方法参数推导的
`gm.f1(new B())` 就推导成 `B f1(B)`,`gm.f1(new A())` 就推导成 `A f1(A)` |
4
amiwrong123 OP @Bromine0x23
可是我觉得类型参数也能通过返回值处的类型参数推断啊。比如: ```java class GenericMethods { public <T> T f2(Object x) { System.out.println(x.getClass().getName()); return (T)x; } } public class testP { public static void main(String[] args) { GenericMethods gm = new GenericMethods(); int o1 = gm.f2(2); String o2 = gm.f2(2);//能通过编译,但运行时报错 } } ``` 这里可以认为,执行`String o2 = gm.f2(2)`后,由于这里`T`就被推断为了`String`,一个实际为 2 的 Object 对象在向上转型为`String`类型后,执行(String)x,这句会受到 RTTI 的检查,被发现无法转型后便报错。 |
5
Bromine0x23 2019-09-07 17:31:03 +08:00 1
@amiwrong123
详细说的话是 f2 的 T 参数在信息不足的情况下被推延了。 这个推导是分段进行的,首先是对 gm.f2(2),由于 T 没出现在参数中所以这里不能确定(或者说可选范围是 <= Object ) 然后 int o1 = <T> 或者 String o2 = <T>,在就能确定 T 的类型了 对前面 B b = gm.f1(new A()), 在 gm.f1(new A()) 中已经能确定 T = A 了,所以之后 B b = <A> 的表达式由于类型不匹配导致编译失败 |
6
amiwrong123 OP @Bromine0x23
谢谢回答。但 A a = gm.f1(new B());这里该怎么理解呢,照你这么说,意思就是,有了方法参数的推断,就不需要返回值的推断了。那这里 f1 的 T 就被推断为 B 了呗。 只是 gm.f1(new B())返回了一个 B 对象,然后由于赋值,向上转型为了一个 A 对象。 |
7
Bromine0x23 2019-09-07 17:40:31 +08:00
@amiwrong123 是的
|
8
cigarzh 2019-09-07 19:42:12 +08:00 via iPhone
你这报错和泛型有啥关系……
|
9
amiwrong123 OP @cigarzh
主要类型参数推断理解错了 |
10
axlecho 2019-09-07 21:08:01 +08:00 via Android
A a = new B ok
B b = new A failed 这里跟泛型没关系 |
11
fengpan567 2019-09-07 21:17:00 +08:00
和泛型没关系。这个是 B 是子类,A 是父类,父类的实例引用不能指向子类,但是子类实例引用是可以指向父类的
|
12
ninjachen 2019-09-07 21:52:24 +08:00 via Android
乡下转型。。。
这个是什么词汇? 只听过强行 cast,向下是不可能自动做的 |
13
ninjachen 2019-09-07 21:52:45 +08:00 via Android
乡下 typo,是向下
|
14
amiwrong123 OP @ninjachen
不好意思,创造了个新词汇 囧 rz |
15
jxie0755 2019-09-08 09:01:16 +08:00
泛型和多态好像没有什么很相关的地方? 虽然都是面向对象编程里的概念
泛型是针对容器做出的设定 多态是针对继承关系做出的设定 如果你一定要对几个容器之间做继承关系的话.........那你参考下 Collection 和 Arraylist 之间是怎么个安排的吧? |
16
lolizeppelin 2019-09-08 10:57:58 +08:00
你先用 python 之类的动态语言写一遍
再用 java 这样的静态语言写一遍 然后你就理解为什么静态语言需要泛型这玩意了 不要死脑筋去理解,功能做出来是为了解决痛点的,你知道为什么痛了,自己然就懂了 |
17
amiwrong123 OP |
18
oneisall8955 2019-09-08 12:46:14 +08:00 via Android
我写了个博客,https://liuzhicong.cn/index.php/study/extends-T-and-super-T.html,
想看精简版本,看借鉴参考的第二篇 stackoverflow 的就行了,私认为解释的很清楚 |
19
amiwrong123 OP |
20
Bromine0x23 2019-09-08 13:29:44 +08:00 1
@amiwrong123
你声明的 gen 是 Generator 而不是 BasicGenerator,当然调用不了 create1 和 test |
21
amiwrong123 OP @Bromine0x23
不好意思,刚才没注意到那个静态方法的返回值。修改静态方法后可执行。 经过测试发现,虽然成员的泛型方法和泛型类用了同一个标识符 T,但是成员泛型方法的 T 并没有被对象的具体类型 CountedObject 所覆盖,而是独立于对象的。 |
22
guyeu 2019-09-08 18:35:08 +08:00
向下转型在 Java 里是不存在的,只能把一个对象转为对象真正的类型或者它的派生类。
你可以把泛型理解为一个语法糖,这个语法糖的作用只是做一种类型提示,告诉编译器这个地方可能是什么类型,帮助用户和编译器做类型推断来检查一些错误。 |
23
Justin13 2019-09-08 19:39:24 +08:00 via Android
我的理解是泛型就是多态的一种,泛型函数更关注数据结构(接口)而非具体的数据类型。
|
24
godloveplay 2019-09-25 18:45:20 +08:00
@guyeu #22 "只能把一个对象转为对象真正的类型或者它的派生类"
这个派生类是说反了吗? class B extend A B 是 A 的派生类,A 是 B 的基类,B 由 A 派生出来 只能把一个对象转为对象真正的类型或者它的基类? |
25
guyeu 2019-09-25 20:17:57 +08:00
@godloveplay #24 对对对,应该是基类。
|
26
guyeu 2019-09-25 20:19:02 +08:00
@godloveplay #24 对象的类型并不会发生改变,改变的是引用的类型。
|
27
godloveplay 2019-09-25 23:26:23 +08:00
|
28
guyeu 2019-09-26 13:44:01 +08:00
@godloveplay #27 这个对象本来就是 A,强转也不过是是检查一下对象的类型
|