内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用
我有这样的代码:
Set<? extends Notifiable> notifiables;
应通知的是一个接口。我不明白上面的代码和:
Set<Notifiable> notifiables;
如果Notifiable是一个类,那么我会理解这种差异,第一个代码将允许Notifiable和任何Notifiable的子类,而第二个代码只允许Notifiable(而不是任何子类)
因为你不能有一个接口的实例,我可以添加/ etc到集合中?在我看来,只有两种选择,不管是实现Notifiable的东西(在这种情况下,与第一个代码有什么不同),或者只有“Notifiable的实例”,它们不能存在,所以什么都没有(这是没有意义的,应该会引发编译时错误)。
让我们用一个更直接的例子:
Set<? extends Serializable> serializables;
这一个声明一个变量,它可以保存Set
对Integer
s,Float
s等的引用:
serializables = new HashSet<Serializable>(); // valid
serializables = new HashSet<Number>(); // this is valid as well
serializables = new HashSet<Integer>(); // valid
另一方面:
Set<Serializable> serializables;
只能容纳Set
的Serializable
对象但是:
serializables = new HashSet<Serializable>();
serializables = new TreeSet<Serializable>();
所以这一个将是一个编译器错误:
List<Serializable> numbers = new ArrayList<Integer>();
推论:
如果你想要一个可以容纳任何子类型的 字段Notifiable
然后使用:
Set<Notifiable> notifiables = new HashSet<Notifiable>();
如果你想限制Notifiable
可以使用的子类型,那么这是一条路:
Set<? extends Notifiable> notifiables = new HashSet<MyNotifiable>();
附录:
这是完全合法的,所以你可以Set
在你认为合适的时候进行改装:
Set<? extends Notifiable> notifiables = new HashSet<NotifiableA>();
notifiables = new HashSet<NotifiableB>();
A Set<Notifiable>
可以容纳实现的类的实例Notifiable
。它并不局限于只持有具体类型的实例Notifiable
(并且你是对的,没有这种东西)。但它Set<Notifiable>
保证它可以拥有任何类型的Notifiable
,因为它有一个add(Notifiable)
方法可以接受任何实现接口的方法。
假设你有一些调用的类Foo
,Bar
并且都实现了Notifiable
。如果你创建了一个Set<Foo>
- 也就是只允许包含实例Foo
及其子类型的集合- 你不能将它传递给带有的方法Set<Notifiable>
,因为该方法可能会添加非Foo
实例的东西,比如Bar
。
public void addABar(final Set<Notifiable> notifiables) {
notifiables.add(new Bar()); // OK, since Bar is a subtype of Notifiable
}
public void wontWork() {
final Set<Foo> foos = new HashSet<>();
addABar(foos); // Compile error, can't convert Set<Foo> to Set<Notifiable>
}
但是有时候,你要编写一个方法可以接受的事情像Set<Foo>
和Set<Bar>
除Set<Notifiable>
。这就是通配符出现的地方。Set<? extends Notifiable>
保证所有内容都是某种类型的Notifiable
,但并不能保证Notifiable
可以添加任何类型的内容; 它可以被限制为一个子类型。您不能调用add()
它,因为该方法现在add(? extends Notifiable)
不是,add(Notifiable)
而且您无法调用其参数类型未知的方法。
当你不需要添加元素时,你通常会使用它,但你需要查看现有的元素并调用Notifiable
它们的接口方法,并且你希望允许调用者传递一些子类型的集合,例如Set<Foo>
。
例如:
public void notifyAll(final Set<? extends Notifiable> notifiables) {
for (final Notifiable notifiable : notifiables) {
notifiable.notify();
}
}
public void example() {
final Set<Foo> foos = whatever();
notifyAll(foos); // OK, since a Set<Foo> is a Set<? extends Notifiable>
}
如果notifyAll()
拿了一个Set<Notifiable>
,你将无法传递foos
给它。