Collections 里面有一段代码
private static <T> T get(ListIterator<? extends T> i, int index) {
T obj = null;
int pos = i.nextIndex();
if (pos <= index) {
do {
obj = i.next();
} while (pos++ < index);
} else {
do {
obj = i.previous();
} while (--pos > index);
}
return obj;
}
他这里用了 <T> T get(ListIterator<? extends T> i)
我有点不理解,因为 T
就是传入的 ListIterator
的泛型定的,根本没有再向下转型的可能了。
所以应该和 <T> T get(ListIterator<T> i)
是一样的,那为什么要多此一举呢?
1
lhx2008 OP 还有很多,比如
public static <T> void copy(List<? super T> dest, List<? extends T> src) 第一个参数可不可以也变成 List<T> dest |
2
ywcjxf1515 2018-12-06 00:30:56 +08:00 via iPad
如果类 A 实现或者继承了 T,那么 ListIterator < A >对象也是能传进这个方法的,那么之后,obj 指向的是一个子类对象,也就是类型 A 的对象,这与返回值类型是兼容的
|
3
FrankFang128 2018-12-06 01:06:17 +08:00
List<? extends T> 有多种可能,但是 List<T> 只有一种可能。
|
4
chocotan 2018-12-06 01:20:12 +08:00
不一样的
给方法再加个 T 类型参数就能看出来了 |
5
ryougifujino 2018-12-06 01:21:51 +08:00 via Android
不是多此一举吧,假如传入的是子类,但是返回的数据可以向上转型呀
|
6
ryougifujino 2018-12-06 01:24:28 +08:00 via Android
“因为 T 就是传入的 ListIterator 的泛型定的,根本没有再向下转型的可能了。”这里应该是向上转型才对
|
7
lhx2008 OP |
9
lhx2008 OP 谢谢大家回复,我知道是可以扩大范围,但是这个 T 就是在变量传入那个时刻才确定的,不是提前确定好,然后你第二次传进去的时候可以传一个父类或者子类的东西。(比如像 Stream 里面的)
我有个想法,如果要证明这两种写法确实不一样,可以写一段代码调用这两种参数的写法,一个可以传进去,一个传不进去,但是我想不出来。 |
10
wqlin 2018-12-06 08:50:53 +08:00 1
谈一下我的理解。
Java 类型系统中 数组 和 集合 是会让人迷惑的。比如有两个类型,A 和 B,其中 A 是 B 的子类。那么 []A 也是 []B 的子类,可以这么写 ``` []A a = {...}; []B b = a; ``` 但是使用集合时,比如 List。List<A> 和 List<B> 没有半毛钱关系,这两个类型完全没有联系。 那么如何在集合中表达类型的上下限呢?就需要用到 ? 占位符、extends 和 super。 ? 是类型占位符,表示这是一个类型,但是具体什么类型未知。比如 List<?> 表示一个未知类型的 List,但是这不是 raw List。 ? 通常和 extends、super 一起使用。作为方法参数时,比如 List<T>,那么 List<? extends T> 可以接受任何 List<E>,其中 E 是 T 的子类,类型上限为 T ; List<? super T> 可以接受任何 List<E>,E 是 T 的超类,类型下线为 T。 一个例子是实现 泛型 Number 相加 ``` static long sum(List<? extends Number> numbers) { long summation = 0; for (Number number : numbers) { summation += number.longValue(); } return summation; } ``` 那么 List<Integer>、List<Double> 等,传入 sum 中: ``` List<Integer> myInts = asList(1, 2, 3, 4, 5); List<Long> myLongs = asList(1L, 2L, 3L, 4L, 5L); List<Double> myDoubles = asList(1.0, 2.0, 3.0, 4.0, 5.0); System.out.println(sum(myInts)); System.out.println(sum(myLongs)); System.out.println(sum(myDoubles)); ``` 如果单纯定义为: ``` static long sum(List<Number> numbers) { long summation = 0; for (Number number : numbers) { summation += number.longValue(); } return summation; } ``` 是没有什么意义的,这时传入 myInts 和 myLongs 会产生编译错误。 ``` public static <T> void copy(List<? super T> dest, List<? extends T> src) ``` 那么实例化时,比如说 T 是 Number。那么可以将 List<Integer> 拷贝到 List<Number> 甚至 List<Object> 中: ``` List<Integer> myInts = asList(1,2,3,4); List<Double> myDoubles = asList(3.14, 6.28); List<Object> myObjs = newArrayList<Object>(); copy(myInts, myObjs); copy(myDoubles, myObjs); ``` 最后谈一下 PECS 原则。如果我们只想从集合中读取元素,那么应该使用协变;如果我们只想向集合中加入元素,那么应该使用逆变,这也被称为 PECS 原则 (Produer Extends, Consumer Super)。 楼主如果感兴趣的话,还可以搜一下 Java 类型系统的协变、逆变看一下 |
11
HiJackXD 2018-12-06 08:54:56 +08:00 via iPhone
@lhx2008 如果现在又有个方法 只支持传入 T 及其父类 ,那么楼主例子中的方法返回的对象就必须手动转一次才能传入这个方法。
|
12
lhx2008 OP @wqlin 就拿 copy 函数说吧,T 的类型是第一个参数定的,super 不 super 都一样。第二个参数只要是 extendes 第一个参数的 T 就行了。加了 super 不是画蛇添足?
|
13
wqlin 2018-12-06 09:03:45 +08:00 1
@lhx2008 #12 第二个参数 extends T 是不能调用 add 方法的,会报编译错误的。
比如申明了 ``` List<? extends Number> myNums = new ArrayList<Integer>(); ``` 只能从 myNums 中读取元素,赋值给 Number 类型(还不能是其他类型): ``` Number n = myNums.get(0); ``` 如果调用 add 会直接报编译错误: ``` myNums.add(45L); //compiler error ``` 类似的,super 只能写不能读: ``` List<? super Number> myNums = new ArrayList<>(); myNums.add(1L); // legal myNums.add(0.1); // legal ``` 读会报错: ``` Number myNum = myNums.get(0); //compiler-error ``` |
14
sagaxu 2018-12-06 09:05:16 +08:00 via Android 1
这是泛型的协变和逆变,pecs 原则了解一下
https://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super JAVA 开发竟然没看过 effective java? |
15
lhx2008 OP @HiJackXD 如果传入 ListIterator<? super Integer> 无论参数加不加 extends T,都返回 Object
如果传入 ListIterator<? super Integer> ,无论加不加 extends T,返回都是 Integer,没有区别 |
16
lhx2008 OP @wqlin 我知道,现在问题不是泛型扩大的问题,我把 copy 函数复制出来
public static <T> void copy(List<? super T> dest, List<? extends T> src) { int srcSize = src.size(); if (srcSize > dest.size()) throw new IndexOutOfBoundsException("Source does not fit in dest"); if (srcSize < COPY_THRESHOLD || (src instanceof RandomAccess && dest instanceof RandomAccess)) { for (int i=0; i<srcSize; i++) dest.set(i, src.get(i)); } else { ListIterator<? super T> di=dest.listIterator(); ListIterator<? extends T> si=src.listIterator(); for (int i=0; i<srcSize; i++) { di.next(); di.set(si.next()); } } } 我们,现在改它第一个参数的泛型,改成 T public static <T> void copy(List<T> dest, List<? extends T> src) { int srcSize = src.size(); if (srcSize > dest.size()) throw new IndexOutOfBoundsException("Source does not fit in dest"); if (srcSize < COPY_THRESHOLD || (src instanceof RandomAccess && dest instanceof RandomAccess)) { for (int i=0; i<srcSize; i++) dest.set(i, src.get(i)); } else { ListIterator<? super T> di=dest.listIterator(); ListIterator<? extends T> si=src.listIterator(); for (int i=0; i<srcSize; i++) { di.next(); di.set(si.next()); } } } 我的前提想法是,这两个函数没有区别,所以我得出的结论是,第一个函数加 super T 是没有意义的。 |
17
aaronysj 2018-12-06 09:14:57 +08:00
不是一个可以能传子类对象一个只能传自己吗
|
18
lhx2008 OP 突然在 stackoverflow 上面看到了 https://stackoverflow.com/questions/34985220/differences-between-copylist-super-t-dest-list-extends-t-src-and-co
总算有人和我想法一样了,好累 |
19
liuxey 2018-12-06 09:20:14 +08:00
这就是类型限定,第一个入参 List 泛型可以是 T 的子类,而第二个只能是 List<T>
用 wqlin 的例子就是第一个能传 List<Number> List<Integer> List<Double> 等。。然后返回 Number 而第二种写法只能传 List<Number>返回 Number |
20
wqlin 2018-12-06 09:26:20 +08:00 via Android
@lhx2008 有区别啊。比如说 T 是 Number,你的 copy 只能将 List<Integer> 拷贝到 List<Number> 中。那如果我想将
List<Integer> 拷贝到 List<Object> 就要用第一种写法了 |
21
lhx2008 OP @wqlin 但是 T 不是你想定就定的,是按第一个参数的泛型定的
你传 copy (List<Object> ,List<Integer>) ,T 就是 Object 你传 copy(List<Number>, List<Integer> ),T 就是 Number 都没有问题 |
22
szq8014 2018-12-06 09:33:47 +08:00
协变 & 逆变 +1
|
23
wqlin 2018-12-06 09:40:05 +08:00 via Android
@lhx2008 我到没怎么用过 Collections 中的 get 函数,只是表达下 T 和 ? super T 区别。不过为啥是第一个参数而不是第二个参数?
|
24
Foredoomed 2018-12-06 09:42:08 +08:00
super 的作用并不是你能放什么类型在这个集合里,而是指定了集合的引用类型,具体的自己网上搜吧。
|
25
LucasLee92 2018-12-06 09:43:11 +08:00
@lhx2008 两个例子不都是用父类来接受子类,java oo 思想的一种表现
|
27
Raymon111111 2018-12-06 10:36:04 +08:00
可以传 Integer 返回 Number 啊
|
28
FrankFang128 2018-12-06 13:54:38 +08:00
@lhx2008 List<T> 只能把元素当做 T 操作,List<? extends T> 可以把元素当做 T 的子类操作,还是不一样的
|