5

我查看了问题q1q2q3,但它们并没有完全涵盖我的问题。

请注意,ArrayList<A> and ArrayList<? extends A>用于声明变量或参数(不用于创建新的泛型类)。

声明对象属性时,这两个表达式是否等效(案例 1)?:

class Foo {

  private ArrayList<A> aList; // == ArrayList<? extends A> aList;
}

编辑:从允许将什么样的对象添加到的角度来看,这两个表达式是否等效aList?,但在以下情况下的意义不同?

但是在参数声明中使用时它们是不同的(案例2)?:

void methodFoo(ArrayList<A> al)  !=  void methodFoo(ArrayList<? extends A> al)

因为第一个只允许传递 ArrayList 对象,而第二个就像“更宽松”允许发送 ArrayList<A1>ArrayList<A2>(只要 A1 和 A2 扩展 A)?

如果这是正确的,是否还有其他两种表达方式实际上不同的情况?

谢谢,

4

6 回答 6

8

让我们看一些实际的例子。说,你有:

List<Number> list;

这意味着分配给此变量或字段的任何内容都需要Number和输出Number,因此您始终知道会发生什么。Integer可以添加到此列表中,因为Integerextends Number。但是,您不能分配ArrayList<Long>给这个列表。

但是考虑一下这种情况:

List<? extends Number> list;

这个说:嘿,这是一个扩展的东西的列表Number,但没有人知道究竟是什么。这是什么意思?这意味着您可以分配ArrayList<Long>给这个列表,例如,在第一种情况下您不能。您仍然知道此列表输出的内容将是 a Number,但您不能再将aInteger放入其中。

还有一个相反的情况:

List<? super Number> list;

通过打印你说:这是一个列表Number或其超类。这就是一切反之亦然的地方。该列表现在可以参考ArrayList<Object>ArrayList<Number>。现在我们不知道这个列表会输出什么。会是一个Number吗?会是一个Object吗?但是现在我们知道我们可以aNumber以及NumberlikeInteger或的任何子类放入此列表中Long

顺便说一句,有一条规则,说producer extends,consumer super(简称PECS)。如果您需要列表来输出值,它是生产者,这是第二种情况。如果你需要列表接受值,它是一个消费者,这是第三种情况。如果两者都需要,请不要使用通配符(这是第一种情况)。

我希望这能解决问题。

于 2012-04-14T10:40:26.653 回答
4

这将解释差异:

public class GenericsTest {
  private ArrayList<A> la;
  private ArrayList<? extends A> lexta;

  void doListA(ArrayList<A> la) {}
  void doListExtA(ArrayList<? extends A> lexta) {}

  void tester() {
    la = new ArrayList<SubA>(); // Compiler error: Type mismatch
    doListA(new ArrayList<SubA>());  // Compiler error: Type mismatch
    lexta = new ArrayList<SubA>();
    doListExtA(new ArrayList<SubA>());
  }

  static class A {}
  static class SubA extends A {}
}

如您所见,调用方法和分配变量/实例字段具有相同的规则。将方法调用视为将参数分配给其声明的参数。

于 2012-04-14T10:39:44.473 回答
1

private ArrayList<A> aList;用作变量声明并不等同于使用通配符private ArrayList<? extends A> aList;

通配符版本将允许您分配扩展 A 和 A 本身的任何类型的 ArrayLists,但会拒绝将元素添加到列表中,因为它无法确定它是否是类型安全的。ArrayList<A>另一方面,您只能分配 A 类型的 ArrayList(或 ArrayList 的扩展),然后您可以添加 A 元素和任何扩展 A 的元素。

仅供参考:您应该更喜欢使用更抽象的类型来声明变量/参数,例如List<A>or Collection<A>

于 2012-04-14T10:24:01.420 回答
1

ArrayList<A>表示特定的 A 类,其中 asArrayList<? extends A>表示 A 类或任何扩展 A 的类(A 的子类),这使其更通用

于 2012-04-14T10:04:22.467 回答
0

主要区别在于,如果泛型形式用作基类(或接口)中方法的参数或返回类型,则它允许更大范围的类型签名计为覆盖而不是重载。

Java中的函数重载

例如,以下代码是合法的(在 Java 7 中):

interface A
{
    List<? extends Number> getSomeNumbers();
}

class B implements A
{
    @Override
    public ArrayList<Integer> getSomeNumbers()
    {
        return new ArrayList<>();
    }
}

枚举<? 扩展 ZipEntry> 和 Enumeration<ZipEntry>?

所有这一切意味着,有时您可以编写在其他人使用时需要较少强制转换的代码。这不仅应该减少他们必须做的打字量,而且还应该消除可能的失败。

当然,Java 泛型的问题在于它们是以一种受向后兼容性限制的方式引入的。因此,并非一切都像您认为应该的那样有效,而且关于哪些具体有效,哪些无效,细节变得非常棘手。

http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ812

于 2012-04-14T11:22:25.920 回答
0

一开始很难掌握,但继承不适用于泛型,即如果 B 扩展 A,List<B>则不是 (不能分配给) 的“子类” List<A>。此外,List<? extends A>不是(不能分配给)的“子类” List<A>

于 2012-04-14T10:54:23.543 回答