【问题标题】:Interfaces and generics in JavaJava中的接口和泛型
【发布时间】:2014-03-23 18:35:54
【问题描述】:

我有代码:

Set<? extends Notifiable> notifiables;

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

Set<Notifiable> notifiables;

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

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

【问题讨论】:

  • +1 好问题,我试图让它工作,但我自己也无法做到。
  • 您似乎误解了通配符的含义。通配符与可以将哪种Notifiable 添加到集合中无关,而是可以将哪种Set 放入引用变量中。实际上,您无法向Set&lt;? extends Notifiable&gt; 添加任何内容。
  • @radiodef,我的意思是使用通配符声明的变量和没有声明的变量之间的区别?

标签: java generics interface


【解决方案1】:

Set&lt;Notifiable&gt;可以保存实现Notifiable 的类的实例。它不仅限于仅保存具体类型为 Notifiable 的实例(你是对的,没有这样的东西)。但是Set&lt;Notifiable&gt; 保证它可以容纳任何Notifiable,因为它有一个add(Notifiable) 方法可以接受任何实现该接口的东西。

假设您有一些名为FooBar 的类都实现了Notifiable。如果你创建了一个Set&lt;Foo&gt;——也就是说,一个允许包含Foo及其子类型实例的集合——你不能将它传递给一个采用Set&lt;Notifiable&gt;的方法,因为该方法可能会添加不是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&lt;Foo&gt;Set&lt;Bar&gt;之类的方法,除了Set&lt;Notifiable&gt;。这就是通配符的用武之地。Set&lt;? extends Notifiable&gt; 保证其中的所有内容都是某种Notifiable,但它保证可以将所有类型的Notifiable 添加到其中;允许将其限制为子类型。你不能在上面调用add(),因为这个方法现在是add(? extends Notifiable)而不是add(Notifiable),你不能调用参数类型未知的方法。

当您不需要添加元素时,通常会使用它,但您确实需要查看现有元素并在它们上调用Notifiable 接口方法,并且您希望允许调用者传递集合Set&lt;Foo&gt; 等子类型。

例如:

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&lt;Notifiable&gt;,您将无法将foos 传递给它。

【讨论】:

  • 这里的问题是,是否有一个Set&lt;? extends Notifiable&gt; 可以存在?如果你能找到这样的案例,那我很乐意看到。
  • @skiwi,是的,每个Set&lt;Notifiable&gt; 都是Set&lt;? extends Notifiable&gt;。每个Set&lt;Foo&gt;Set&lt;Bar&gt; 也是如此。
  • 主要区别在于Set&lt;Foo&gt;不是Set&lt;Notifiable&gt;,因为它不支持像add(Notifiable) 这样可用于添加@ 的方法987654359@。但它 Set&lt;? extends Notifiable&gt;,因为它不需要支持添加其他类型的通知。
  • 我在打电话,只是略读了你的答案。但是您好像在谈论将集合添加到集合中?
  • @Jonathan.,不,将单个项目添加到集合中。
【解决方案2】:

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

Set<? extends Serializable> serializables;

这个声明了一个变量,它可以持有对SetIntegers、Floats 等的引用:

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

【讨论】:

  • Number 不是一个接口,所以这并不能真正解决问题。
  • Same 代表接口。
  • 当然,您可以将Set&lt;Notifiable&gt; 的任何子类型添加到此数据结构中,正如我在回答中所说的那样。编辑它以澄清问题。
  • Set&lt;? extends Notifiable&gt; notifiables = new HashSet&lt;MyNotifiable&gt;(); 为什么不在变量声明中使用 MyNotifiable 呢?我也看不出这如何将其限制为 MyNotifiable?我以为类型检查只是在变量声明上完成的?
【解决方案3】:

对于 Collection 类型中的一级泛型,? extends 通配符的作用不是很重要,因为所有 Collection 方法看起来都像

E get(int index);
boolean add(E e);

实际上与以下内容相同:

? extends E get(int index);
boolean add(? extends E e);

由于 Java 子类型多态规则。

但是,当子类型规则发挥作用时,通配符很重要:

    List<Integer> list = Arrays.asList(1);
    List<? extends Number> numSubTypeList = list; // Works
    List<Number> numList = list; // Illegal

对于多级泛型也是如此:

    Collection<Collection<Number>> col1 = new ArrayList<Collection<Number>>();
    col1.add(list); // Illegal

    Collection<Collection<? extends Number>> col2 = new ArrayList<Collection<? extends Number>>();
    col2.add(list); // Works

然而,即使col2 的签名也不适合泛型库。这些人可能会期望Collection&lt;? extends Collection&lt;? extends Number&gt;&gt;col2 是子类型。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多