正如肯尼在他的评论中指出的,你可以通过以下方式解决这个问题:
List<Test<? extends Number>> l =
Collections.<Test<? extends Number>>singletonList(t);
这立即告诉我们该操作不是unsafe,它只是有限的受害者推理。如果不安全,上面的代码就无法编译。
由于在上面的泛型方法中使用显式类型参数只需要充当hint,我们可以推测这里需要的是推理引擎的技术限制。事实上,Java 8 编译器目前预计将附带对类型推断的许多改进 http://openjdk.java.net/jeps/101。我不确定您的具体情况是否会得到解决。
那么,到底发生了什么?
好吧,我们收到的编译错误表明类型参数T
of Collections.singletonList
被推断为capture<Test<? extends Number>>
。换句话说,通配符具有一些与其关联的元数据,将其链接到特定的上下文。
- 考虑捕获通配符的最佳方式(
capture<? extends Foo>
) 是作为unnamed相同边界的类型参数(即<T extends Foo>
,但无法参考T
).
- “释放”捕获功能的最佳方法是将其绑定到泛型方法的命名类型参数。我将在下面的示例中演示这一点。请参阅 Java 教程“通配符捕获和辅助方法” http://docs.oracle.com/javase/tutorial/java/generics/capture.html(感谢@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>
, not ? extends String
。如果我们改为使用循环的非通用实现:
public static List<? extends String> cycle(List<? extends String> list) {
list.add(list.remove(0));
return list;
}
它将无法编译,因为我们没有通过将捕获分配给类型参数来使其可访问。
所以这开始解释为什么消费者singletonList
将受益于类型推断器解析T
to Test<capture<? extends Number>
,从而返回一个List<Test<capture<? extends Number>>>
代替List<Test<? extends Number>>
.
但为什么一个不能分配给另一个呢?
为什么我们不能只分配一个List<Test<capture<? extends Number>>>
to a List<Test<? extends Number>>
?
好吧,如果我们考虑一下这个事实capture<? extends Number>
相当于一个匿名类型参数,其上限为Number
,那么我们可以把这个问题变成“为什么下面的代码不能编译?” (事实并非如此!):
public static <T extends Number> List<Test<? extends Number>> assign(List<Test<T>> t) {
return t;
}
这有充分的理由不编译。如果是的话,那么这将是可能的:
//all this would be valid
List<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;
好吧,这不是有效的语法,因为我们无法显式引用捕获,所以让我们使用与上面相同的技术来评估它!让我们将捕获绑定到“分配”的不同变体:
public static <T extends Number> Test<? extends Number> assign(Test<T> t) {
return t;
}
这样就编译成功了。不难看出为什么它应该是安全的。这就是类似的用例
List<? extends Number> l = new List<Double>();