在Java中,泛型是不变的?

时间:2015-01-08 12:15:11

标签: java generics

编译以下代码失败:

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>吗?这是如何运作的?

3 个答案:

答案 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;应该接受你投掷的任何东西。

因为类型未知,所以无法使用它执行操作,需要知道元素的类型(如setadd等)。

List<E>是通用的。它与List<?>不同,即使它看起来有点类似。 一个区别是,正如您所指出的,您可以对其执行setadd之类的操作,因为现在已知该元素的类型。

作为函数参数的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在这种情况下,调用者必须以某种方式指定期望的类型。