12

在下面的代码中,get()被调用并将其结果分配给类型为 的变量List<List<?>>get()返回 aList<List<T>>并在其类型参数T设置为的实例上调用?,因此它应该适合。

import java.util.List;

class Test {
    void foo(NestedListProducer<?> test) {
        List<List<?>> a = test.get();
    }

    interface NestedListProducer<T> {
        List<List<T>> get();
    }
}

但是 IntelliJ IDEA 和 Oracle 的javac1.7.0_45 版本都拒绝我的代码无效。这是“javac”的错误消息:

java: incompatible types
  required: java.util.List<java.util.List<?>>
  found:    java.util.List<java.util.List<capture#1 of ?>>

为什么此代码无效,即如果允许,会出现什么问题?

4

4 回答 4

7

?是一个通配符,表示任何类型。One?不能与 another 相同?,因为 another?可以是任何其他类型,并且它们不匹配。您必须使用泛型来表示类型相同:

// Make this generic
<A> void foo(NestedListProducer<A> test) {
    List<List<A>> a = test.get();
}
于 2013-11-14T17:53:12.687 回答
4

List<List<T>>表示您可以读取List<T>或写入 newList<T>的列表,类似地List<List<?>>表示您可以读取List<?>或写入 newList<T>的列表。奇怪的?是,您可以将任何类型的列表转换SList<?>. 例如你可以写:

void foo(List<String> a, List<Integer> b, List<List<?>> out) {
  List<?> unknownA = a;
  List<?> unknownB = b;
  out.add(a);
  out.add(b);
}

如果可以将 a 转换List<List<T>>为 a ,则可以使用 aList<List<?>>调用,然后向其中添加字符串和整数列表。fooList<List<PeanutButter>>

通常人们会遇到这种情况,因为他们试图表达他们想要一个类型无关紧要的子集合集合的概念。如果这是您想要的,您可以将类型从 更改List<List<?>>List<? extends List<?>>,这表达了我可以读取但不能写入的子列表列表的概念。List<List<T>>将 a 转换为 a是合法的List<? extends List<?>>

于 2013-11-14T18:33:49.387 回答
4

您似乎对编译器如何处理 aList<?>和 a感到困惑List<List<?>>。AList<?>是某种未知List类型的一个,而,是一个(不同类型的未知)。List<List<?>>ListList

因此,对于List<?>,通配符?表示单个未知类型,因此编译器会将其捕获到该单个未知类型的占位符中。在 中List<List<?>>,通配符?代表不同的未知类型。编译器不会捕获此类类型,因为不同的未知类型不能有一个占位符。

现在考虑您的原始示例:

void foo(NestedListProducer<?> test) {
    List<List<?>> a = test.get();
}

在这种情况下,编译器将在编译时捕获?ofNestedListProducer以创建一个匿名类型参数,并将创建一个类似于以下的辅助方法:

<CAP#1 of ?> void foo_2(NestedListProducer<CAP#1 of ?> test) {
    List<List<?>> a = test.get();
}

(注意:它不会捕获?in List<List<?>>,所以它会保持原样)。

test.get()现在在这种情况下的返回类型将是List<List<CAP#1 of ?>>. 这不是可转换的赋值捕获List<List<?>>,因此不能分配给它。因此它无法编译。

因此,解决方法是自己添加类型参数,如前所述:

<T> void foo(NestedListProducer<T> test) {
    List<List<T>> a = test.get();
}

从评论中查询:

现在正如您在评论中所问的那样,为什么以下代码有效?

void foo(List<List<?>> arg) { 
    List<List<?>> a = arg; 
}

从上面的解释,你可以猜到 in 形式参数中的通配符?不会List<List<?>>被捕获。因此,分配实际上是从 aList<List<?>>List<List<?>>,这是有效的。这里没有CAP#1 of ?

于 2013-11-14T19:04:38.150 回答
0

您可以使用此技巧(通配符捕获)

void foo(NestedListProducer<?> test) {
    foo2(test);
}

<T> void foo2(NestedListProducer<T> test) {
    List<List<T>> a = test.get();
}
于 2013-11-14T18:23:03.620 回答