在 Java 泛型是不变的?

In Java Generics are invariant?

编译以下代码失败:

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>呢?这是如何工作的?

答案是通配符。

List<?>List<Object> 不同 而 java 假定两者都是 Object 的集合,前者将匹配任何其他 List<T> 的类型,而后者将不匹配除 List<Object> 之外的任何 List<T> 的类型。

示例:

public void doSomething(List<Object> list);

此函数将仅接受 List<Object> 作为参数。然而这个:

public void doSomething(List<?> list);

将接受任何 List<T> 作为参数。

这在与 generic constraints 一起使用时非常有用。例如,如果您想编写一个操作数字(Integers、Floats 等)的函数,您可以:

public void doSomethingToNumbers(List<? extends Number> numbers) { ... }

因为您正在使用wildcard捕获。

In some cases, the compiler infers the type of a wildcard. For example, a list may be defined as List but, when evaluating an expression, the compiler infers a particular type from the code. This scenario is known as wildcard capture.

多亏了辅助方法,编译器在调用中使用推理来确定捕获变量 T。

List<?> 表示 "list of an unknown type"。你可以为它传递一个 List<String>,不是因为它是一个子类(它不是),而是因为 "unknown type" 应该接受你扔给它的任何东西。

因为类型未知,你不能用它执行操作,需要知道元素的类型(如 setadd 等)。

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

作为函数参数的通配符不是很有用(<E> void foo(List<E> l)void foo(List<?> l) 差别不大)。它们还绕过了所有编译时类型检查,从而违背了泛型的目的,确实应该避免。

通配符更常见(且危害较小)的用法是在 return 类型中:List<? extends DataIterface> getData() 表示 getData return 是一些实现的对象的列表a DataInterface,但不会告诉你它们的具体类型。这通常在 API 设计中完成,以将实现细节与接口隔离开来。通常的约定是,您可以将对象列表 return 由 API 编辑到其他 API 方法,它们将接受并处理它们。这个概念叫做existential types

此外,请注意上面示例中的 getData 可以 return 不同类型的列表,具体取决于某些条件,即在调用方域之外,这与声明为 return List<E>,在这种情况下,调用者必须以某种方式指定预期的类型。