Java 中的接口和泛型

2022-09-03 06:23:49

我有代码:

Set<? extends Notifiable> notifiables;

应通知是一个接口。我不明白上面的代码和:

Set<Notifiable> notifiables;

如果Notifiable是一个类,那么我会理解区别,第一个代码将允许Notifiable和Notifiable的任何子类,而第二个代码将只允许Notifiable(而不是任何子类)

由于您不能拥有接口的实例,因此我可以向集合中添加/etc什么?在我看来,只有两个选项,要么是任何实现Notifiable的东西(在这种情况下,它与第一个代码有什么不同),要么只有“Notifiable的实例”,它不能存在,因此什么都没有(这是毫无意义的,应该抛出编译时错误)。


答案 1

可以保存实现 的类的实例。它不仅限于保留其具体类型的实例(你是对的,没有这样的事情)。但是 a 保证它可以容纳任何类型的 ,因为它有一个可以接受任何实现接口的方法。Set<Notifiable>NotifiableNotifiableSet<Notifiable>Notifiableadd(Notifiable)

假设您有一些调用的类,并且这两个类都实现了 。如果创建一个(即,只允许包含 的实例及其子类型的集合),则无法将其传递给采用 的方法,因为该方法可能会添加不是实例的内容,例如 .FooBarNotifiableSet<Foo>FooSet<Notifiable>FooBar

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>
}

但有时你想写一个可以接受之类的东西的方法。除了 .这就是通配符的用武之地。A保证其中的所有内容都是某种种类的,但它不能保证每一种都可以添加到其中;允许将其限制为子类型。您无法调用它,因为该方法现在是而不是,并且您不能调用参数类型未知的方法。Set<Foo>Set<Bar>Set<Notifiable>Set<? extends Notifiable>NotifiableNotifiableadd()add(? extends Notifiable)add(Notifiable)

当您不需要添加元素时,通常会使用它,但是您需要查看现有元素并在其上调用接口方法,并且您希望允许调用方传递子类型集,例如。NotifiableSet<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


答案 2

让我们用一个更直接的例子:

Set<? extends Serializable> serializables;

这个声明了一个变量,它可以保存对s,s等的a的引用:SetIntegerFloat

 serializables = new HashSet<Serializable>(); // valid
 serializables = new HashSet<Number>(); // this is valid as well
 serializables = new HashSet<Integer>(); // valid

另一方面,这个:

Set<Serializable> serializables;

但是,只能容纳一个对象:SetSerializable

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>();