编译以下代码失败:
public static void swap(List<?> list, int i, int j) {
list.set(i, list.set(j, list.get(i)));
}
像:
Swap.java:5: set(int,capture#282 of ?) in List<capture#282 of ?> cannot be applied to (int,Object)
list.set(i, list.set(j, list.get(i)));
但如果我这样做:
public static void swap(List<?> list, int i, int j) {
swapHelper(list, i, j);
}
private static <E> void swapHelper(List<E> list, int i, int j) { list.set(i, list.set(j, list.get(i)));
}
它的工作完美。
但我在这里有一个基本的疑问。泛型被认为是不变的,因此List<String>
不是List<Object>
的子类型,对吧?
如果是这种情况,那么在上述方法中,我们能够将List<?>
传递给List<E>
吗?这是如何运作的?
答案 0 :(得分:8)
答案是通配符。
List<?>
与List<Object>
不同
虽然java假设两者都是Object的集合,但前者将匹配任何其他List<T>
的类型,而后者与List<T>
之外的任何List<Object>
的类型不匹配。
示例:
public void doSomething(List<Object> list);
此功能仅接受List<Object>
作为参数。但是这个:
public void doSomething(List<?> list);
将接受任何List<T>
作为参数。
与generic constraints一起使用时,这非常有用。例如,如果您想编写一个操纵数字的函数(Integer
s,Float
s等),您可以:
public void doSomethingToNumbers(List<? extends Number> numbers) { ... }
答案 1 :(得分:1)
因为您正在使用wildcard捕获。
在某些情况下,编译器会推断出通配符的类型。对于 例如,列表可以定义为List,但在评估时 表达式,编译器从代码中推断出特定类型。这个 场景称为通配符捕获。
由于辅助方法,编译器使用推理来确定调用中的T(捕获变量)。
答案 2 :(得分:0)
List<?>
表示&#34;未知类型的列表&#34;。你可以为它传递一个List<String>
,而不是因为它是一个子类(它不是),而是因为&#34;未知类型&#34;应该接受你投掷的任何东西。
因为类型未知,所以无法使用它执行操作,需要知道元素的类型(如set
或add
等)。
List<E>
是通用的。它与List<?>
不同,即使它看起来有点类似。
一个区别是,正如您所指出的,您可以对其执行set
或add
之类的操作,因为现在已知该元素的类型。
作为函数参数的Whildcards不是很有用(<E> void foo(List<E> l)
与void foo(List<?> l)
没有太大区别)。它们还绕过了所有编译时类型检查,从而破坏了泛型的目的,应该真的避免。
对于通配符,更常见(且危害较小)的用法是在返回类型中:List<? extends DataIterface> getData()
表示getData
返回一些实现DataInterface
的对象列表,但赢了&# 39;告诉你他们的具体类型。这通常在API设计中完成,以将实现细节与接口隔离。惯例通常是,您可以传递API返回的对象列表到其他API方法,并且它们将接受并处理它们。这个概念称为existential types
。
另外,请注意上述示例中的getData
可以返回不同类型的列表,具体取决于某些条件,在调用者域之外,与声明返回{{1在这种情况下,调用者必须以某种方式指定期望的类型。