鉴于这种:
Set<?> set1 = new HashSet<Great>();
如果您可视化无界通配符的含义,则可以将无界通配符上的开槽类型与 进行比较extends
,因此如果您明确地这样做。
Set<? extends Object> set1 = new HashSet<Great>();
阅读,是伟大的扩展对象吗?是的,这样编译。
然后给出这个:
Set<Class<Great>> set3 = new HashSet<Class<Great>>();
至于它为什么起作用,如果你提取 Set 和 HashSet 的参数,它们是Class<Great>
,这两个Class<Great>
是完全相同的类型。
如果没有通配符,则直接逐字比较类型。
如果我们编写 set3 使其接受协变类型(编译):
Set<? extends Class<Great>> set3a = new HashSet<Class<Great>>();
要阅读,HashSet 的Class<Great>
类型与 Set 的类型兼容还是协变Class<Great>
?是的。因此它编译。
尽管如果它们只是完全相同的类型,当然没有人会编写那种变量声明,但这是多余的。编译器使用通配符来确定赋值右侧的泛型的具体类或接口参数是否与左侧泛型的具体/接口兼容(理想情况下是接口,如下所示)。
List<? extends Set<Great>> b = new ArrayList<HashSet<Great>>();
阅读它,是HashSet<Great>
协变的Set<Great>
吗?是的。因此它编译
所以让我们回到你的代码场景:
Set<Class<?>> set3 = new HashSet<Class<Object>>();
在这种情况下,同样的规则适用,您从最里面开始阅读它,对象是否与通配符兼容?是的。然后,您转到下一个最外面的类型,它恰好没有通配符。因此,在没有通配符的情况下,编译器将在Class<Object>
和之间进行逐字检查Class<?>
,它们不相等,因此会出现编译错误。
如果它在最外面有通配符,那将编译。所以你可能的意思是这个,它编译:
Set<? extends Class<?>> singletonSet = new HashSet<Class<Object>>();
让我们做一个更有启发性的例子,让我们使用接口(类是具体类型),比如 Set。这编译:
List<? extends Set<?>> b = new ArrayList<HashSet<Object>>();
因此,从内到外阅读它以找出该代码编译的原因,并明确执行:
最里面:Object
兼容? Extends Object
吗?是的。
最外层:是否HashSet<Object>
兼容? extends Set<? extends Object>
?是的。
在数字 1 上,它是这个(编译):
Set<? extends Object> hmm = new HashSet<Object>();
在数字 2 上,它是这个(编译):
List<? extends Set<? extends Object>> b = new ArrayList<HashSet<Object>>();
现在让我们尝试删除最外面的通配符,编译器不会进行类型兼容/协变检查,现在将逐字比较。
所以你现在知道下面的答案了,这些可以编译吗?
List<Set<?>> b = new ArrayList<HashSet<Object>>();
// this is same as above:
List<Set<? extends Object>> b = new ArrayList<HashSet<Object>>();
所以你已经猜到了,正确的......这不会编译:-)
要更正上述问题,请执行以下任一操作:
List<? extends Set<? extends Object>> b = new ArrayList<HashSet<Object>>();
List<? extends Set<?>> b = new ArrayList<HashSet<Object>>();
然后,要更正您的代码,请执行以下任一操作:
Set<? extends Class<? extends Object>> singletonSet =
new HashSet<Class<Object>>();
Set<? extends Class<?>> singletonSet = new HashSet<Class<Object>>();