正如Kenny在评论中提到的那样,你可以通过以下方式解决这个问题:
List<Test<? extends Number>> l =
Collections.<Test<? extends Number>>singletonList(t)
这立即告诉我们,该操作并非不安全,而只是受到了有限的推断的影响。如果它是不安全的,上述代码将无法编译。
既然在泛型方法中使用显式类型参数只是作为提示时才需要的,我们可以推测,在此处需要它是推断引擎的技术限制。事实上,Java 8编译器目前计划改进类型推断的许多方面。我不确定你的特定情况是否会得到解决。
那么,实际发生了什么?
好吧,我们得到的编译错误显示Collections.singletonList
的类型参数T
被推断为capture<Test<? extends Number>>
。换句话说,通配符具有与特定上下文相关联的一些元数据。
- 捕获通配符(
capture<? extends Foo>
)最好的理解方式是将其视为未命名类型参数,具有相同的边界(即<T extends Foo>
,但无法引用T
)。
- 发挥捕获的威力的最佳方法是将其绑定到泛型方法的命名类型参数上。我将在下面的示例中演示这一点。请参见Java教程“Wildcard Capture and Helper Methods”(感谢@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>
,而不是
? extends String
。如果我们使用这个非泛型的cycle实现,情况将会如何:
public static List<? extends String> cycle(List<? extends String> list) {
list.add(list.remove(0));
return list;
}
它将无法编译,因为我们没有通过将其分配给类型参数使捕获可访问。
这就开始解释了为什么singletonList
的消费者从类型推断器将T
解析为Test<capture<? extends Number>
而受益,从而返回一个List<Test<capture<? extends Number>>>
而不是List<Test<? extends Number>>
。
但为什么一个不能赋值给另一个?
为什么我们不能将List<Test<capture<? extends Number>>>
简单地分配给List<Test<? extends Number>>
呢?
如果我们考虑到capture<? extends Number>
相当于具有上限为Number
的匿名类型参数,那么我们可以将这个问题转化为“为什么以下代码不能编译?”(它不能!):
public static <T extends Number> List<Test<? extends Number>> assign(List<Test<T>> t) {
return t;
}
这个代码没有编译通过是有充分的理由的。如果它能够编译通过,那么以下情况就会成为可能:
List<Test<Double>> doubleTests = null;
List<Test<? extends Number>> numberTests = assign(doubleTests);
Test<Integer> integerTest = null;
numberTests.add(integerTest);
那么为什么显式的方式有效呢?
让我们回到起点。如果上述内容是不安全的,那么为什么这个是被允许的:
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>();
Collections.<Test<? extends Number>> singletonList(t)
使其编译。 - kennytm?
,您告诉编译器它们不一定相等,如果您希望它们被视为相等,则可以使用T
或其他占位符类型。 - Jason SperskeT
并且有T extends Number
。看起来你正在寻找灵活性以“做正确的事情”,泛型(集合只是对象的集合)被引入以解决这些问题(强制执行某种类型安全性)。 - Jason Sperske