考虑以下2个备用API:
void method(List<?> list)
<T> void method(List<T> list)
我知道他们的内部实现会有很多不同之处,例如List<?>
无法写入列表等。
另外,据我所知,List<?>
将允许任何带有List
的参数化类型作为基本类型。 List<T>
也是如此。
任何人都可以告诉我这两种API接受的输入类型是否存在任何差异。 (不是2个API内部实现差异。)
答案 0 :(得分:3)
内部实现完全相同。事实上,使用javac
编译的两个方法将产生相同的方法字节代码,如果它们完全编译的话。)
但是,在编译期间,第一个方法指定无关关于列表的组件类型,而第二个方法要求组件类型不变。这意味着每当我调用这样的方法时,list
的组件类型将由调用站点使用的任何内容修复。
我可以使用List<String>
调用我的方法,T
在调用期间(从编译器的角度来看)将与String
同义。我也可以使用List<Runnable>
来呼叫它,T
在通话期间与Runnable
同义。
请注意,您的方法不返回任何内容,但根据参数,它可以很好地返回。考虑方法:
<T> T findFirst(Collection<T> ts, Predicate<T> p) { … }
您可以为每个T
使用此方法。 但是它只有在我们的T
对于集合和谓词相同时才有效 - 这就是“不变性”的含义。实际上,您可以指定适用于更多上下文的方法:
<T> T findFirst(Collection<? extends T> ts, Predicate<? super T> p) { … }
这种方法与上面的方法相同,但它接受的类型会更宽松。考虑类型层次结构A extends B extends C
。然后你可以打电话:
Collection<A> cs = …;
Predicate<C> p = …;
B b = findFirst(cs, p);
我们调用ts
协变的类型和p
的类型(在方法签名中)逆变。
通配符(?
)是另一回事。它们可以是有界的(如上面的情况)有界或逆变。如果它们是无界的,编译器实际上需要一个具体的类型来在编译时填写(这就是为什么你有时会得到像“通配符 - #15不匹配通配符 - #17”这样的错误的原因)。具体规则列于Java Language Specification, Section 4.5.1。