考虑这个例子:
static class Generic<E> {}
static void run() {
Generic<?> x = null;
take(x);
}
static <E> void take(final Generic<E> x) {}
在run
中,x
类型的通配符代表某种未知类型T
。可以为E
方法的take
参数指定任何类型。编译器推断将T
分配给E
是安全的。
在第二个例子中,我们将通配符嵌套一层更深:
static void run() {
Generic<Generic<?>> x = null;
take(x);
}
static <E> void take(final Generic<Generic<E>> x) {}
在此示例中,通配符仍表示某种未知类型T
,并且可以为E
参数指定任何类型。为什么编译器不推断可以将T
分配给E
并允许编译该代码?这段代码是否有原则上没有编译?
注意:将第二个示例中的take
方法更改为以下编译:
static <E> void take(final Generic<Generic<? extends E>> x) {}
当Generic<Generic<E>>
无界时,Generic<Generic<? extends E>>
和E
之间是否存在本质区别?
答案 0 :(得分:3)
Generic<Generic<?>>
, Generic<Generic<T>>
并不代表T
。它表示特定类型Generic<Generic<?>>
。对于任何Generic<Generic<?>>
,类型为Generic<Generic<T>>
的对象永远不会是T
类型。
例如,如果您有List<List<?>>
,则可以使用List<String>
,List<Integer>
,List<Object>
等作为元素的列表。 T
没有List<List<T>>
可以执行此操作的类型{。}}。
直观地说,第一个代码段会针对某些未知的take<T>
调用T
,而第二个代码段则需要使用take<?>
作为类型参数专门调用?
,这是禁止。具体来说,我认为Java为capture conversion ?
中的Generic<?>
为第一个片段生成了一个新的类型变量,而第二个片段中的嵌套通配符不会发生这种变量。尽管如此,我还没有完全弄清楚Java Language Specification如何说出类型推断。
答案 1 :(得分:0)
我不确定,但我认为这也适用于Liskov原则。泛型类型是通过类型参数化的泛型类或接口。 Liskov替换原则不适用于参数化类型。 例如,如果你写的话是编译时错误:
List<Numbers> list = new ArrayList<Integers>();
这同样适用于通配符:在使用Generic<Generic<E>>
评估Generic<Generic<?>>
时,Liskov原则会告诉您Generic<E>
和Generic<?>
没有子类型关系。这就是它给出编译时错误的原因,因为它不能从中推断出什么?与E。