正如肯尼(Kenny)在评论中指出的那样,您可以通过以下方法解决此问题:
List<Test<? extends Number>> l = Collections.<Test<? extends Number>>singletonList(t);
这立即告诉我们,该操作并非 不安全 ,只是 推理 受限的受害者。如果不安全,则以上内容将无法编译。
由于仅在上述通用方法中使用显式类型参数才有必要充当 提示 ,因此我们可以推测,此处所要求的显式类型参数是推理引擎的技术限制。实际上,Java
8编译器目前预计将对类型推断进行许多改进。我不确定您的具体情况是否会解决。
那么,实际发生了什么?
那么,编译错误我们得到节目类型参数
T的
Collections.singletonList被推断为
capture<Test<? extendsNumber>>。换句话说,通配符具有一些与之关联的元数据,可将其链接到特定的上下文。
- 想到捕获通配符(
capture<? extends Foo>
)的最佳方法是将其作为具有相同界限的 未命名 类型参数(即<T extends Foo>
,但无法引用T
)。 - “释放”捕获功能的最佳方法是将其绑定到通用方法的命名类型参数。我将在下面的示例中对此进行演示。有关更多信息,请参见Java教程“通配符捕获和帮助程序方法”(感谢@WChargin的引用)。
假设我们要有一个移动列表的方法,将其包装在后面。然后,假设我们的列表具有未知(通配符)类型。
public static void main(String... args) { List<? extends String> list = new ArrayList<>(Arrays.asList("a", "b", "c")); List<? extends String> cycledTwice = cycle(cycle(list));}public static <T> List<T> cycle(List<T> list) { list.add(list.remove(0)); return list;}这可以正常工作,因为
T解决了
capture<? extends String>,而不是
? extendsString。如果我们改为使用以下非通用实现循环:
public static List<? extends String> cycle(List<? extends String> list) { list.add(list.remove(0)); return list;}它将无法编译,因为我们尚未通过将捕获分配给类型参数来使其可访问。
因此,这开始解释了为什么of的使用者
singletonList将从类型推断器解析
T为中受益
Test<capture<? extendsNumber>,并因此返回a
List<Test<capture<? extends Number>>>而不是a
List<Test<?extends Number>>。
但是为什么不能将一个分配给另一个呢?
为什么我们不能仅将a分配
List<Test<capture<? extends Number>>>给
List<Test<? extendsNumber>>?
好吧,如果我们考虑到
capture<? extendsNumber>一个匿名类型参数的上限等于的事实
Number,那么我们可以将这个问题变成“为什么下面的代码不编译?” (不是!):
public static <T extends Number> List<Test<? extends Number>> assign(List<Test<T>> t) { return t;}这有一个不编译的充分理由。如果确实如此,那么这将是可能的:
//all this would be validList<Test<Double>> doubleTests = null;List<Test<? extends Number>> numberTests = assign(doubleTests);Test<Integer> integerTest = null;numberTests.add(integerTest); //type error, now doubleTests contains a Test<Integer>
那么为什么显式工作呢?
让我们回到起点。如果以上内容不安全,则允许这样做:
List<Test<? extends Number>> l = Collections.<Test<? extends Number>>singletonList(t);
为此,它意味着允许以下操作:
Test<capture<? extends Number>> capturedT;Test<? extends Number> t = capturedT;
嗯,这不是有效的语法,因为我们无法显式引用捕获,因此让我们使用与上述相同的技术对其进行评估!让我们将捕获绑定到“ assign”的另一个变体上:
public static <T extends Number> Test<? extends Number> assign(Test<T> t) { return t;}这样编译成功。不难看出为什么它应该是安全的。这是类似
List<? extends Number> l = new List<Double>();



