列表<列表<>&GT?;无法分配到List <list <?>&gt;何时涉及类型参数</list <?> </list <?>

时间:2013-11-14 17:50:37

标签: java generics

在以下代码中,调用get()并将其结果分配给类型为List<List<?>>的变量。 get()返回List<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的javac版本1.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 个答案:

答案 0 :(得分:7)

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

// Make this generic
<A> void foo(NestedListProducer<A> test) {
    List<List<A>> a = test.get();
}

答案 1 :(得分:4)

List<List<T>>表示您可以阅读List<T>或撰写新List<T>的列表,同样List<List<?>>表示您可以阅读的列表{{ 1}}来自或写新的List<?>。关于List<T>的奇怪之处在于您可以将任何类型?的列表转换为S。例如,你可以写:

List<?>

如果您可以将void foo(List<String> a, List<Integer> b, List<List<?>> out) { List<?> unknownA = a; List<?> unknownB = b; out.add(a); out.add(b); } 转换为List<List<T>>,则可以使用List<List<?>>调用foo,然后向其中添加字符串和整数列表。

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

是合法的

答案 2 :(得分:4)

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

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

现在考虑你原来的例子:

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

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

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

(注意:它不会捕获?中的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; 
}

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

答案 3 :(得分:0)

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

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

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