【问题标题】:What is the practical difference between "ArrayList<A>" and "ArrayList<? extends A>"?“ArrayList<A>”和“ArrayList<? extends A>”之间的实际区别是什么?
【发布时间】:2012-04-15 16:58:43
【问题描述】:

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

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

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

class Foo {

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

编辑:从什么的角度来看,这两个表达式是否等效 kind 对象允许添加到aList?,但不同 和下面的情况一样吗?

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

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

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

如果这是正确的,是否有任何其他情况,这两个表达式是 实际不同?

谢谢,

【问题讨论】:

  • 据我所知,没有区别。

标签: java generics


【解决方案1】:

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

List<Number> list;

这意味着分配给此变量或字段的任何内容都采用Number 并输出Number,因此您始终知道会发生什么。 Integer 可以添加到此列表中,因为 Integer 扩展了 Number。但是,您不能将 ArrayList&lt;Long&gt; 分配给此列表。

但是考虑一下这种情况:

List<? extends Number> list;

这个说:嘿,这是一个扩展 Number 的东西的列表,但没有人知道具体是什么。这是什么意思?这意味着您可以将 ArrayList&lt;Long&gt; 分配给此列表,而在第一种情况下您不能。你仍然知道无论这个列表输出都是Number,但你不能一个Integer放进去。

还有一种相反的情况:

List<? super Number> list;

通过打印您说:这是Number 或其超类的列表。这就是一切反之亦然的地方。该列表现在可以参考ArrayList&lt;Object&gt;ArrayList&lt;Number&gt;。现在我们不知道这个列表会输出什么。会是Number 吗?会是Object 吗?但现在我们知道我们可以在这个列表中放入一个Number 以及Number 的任何子类,例如IntegerLong

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

我希望这能解决问题。

【讨论】:

  • @Malcom:很好地解释了可能是什么类型的东西,或者不添加到list。案例 2:一个方法可以接受什么类型的列表呢?
  • @cibercitizen1 正如我在回答中所说,扩展Number 的内容列表。它可能类似于List&lt;Long&gt;List&lt;Integer&gt;。因此你知道这个列表会给你一个Number,但是你不知道你可以在里面放什么。
【解决方案2】:

这将解释差异:

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

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

【讨论】:

    【解决方案3】:

    ArrayList&lt;A&gt; 表示特定的 A 类,而 ArrayList&lt;? extends A&gt; 表示 A 类或任何扩展 A 的类(A 的子类),这使其更通用

    【讨论】:

    【解决方案4】:

    使用private ArrayList&lt;A&gt; aList;作为变量声明并不等同于使用通配符private ArrayList&lt;? extends A&gt; aList;

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

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

    【讨论】:

    【解决方案5】:

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

    Function override-overload in Java

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

    interface A
    {
        List<? extends Number> getSomeNumbers();
    }
    
    class B implements A
    {
        @Override
        public ArrayList<Integer> getSomeNumbers()
        {
            return new ArrayList<>();
        }
    }
    

    Difference between Enumeration<? extends ZipEntry> and Enumeration<ZipEntry>?

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

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

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

    【讨论】:

      【解决方案6】:

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

      【讨论】:

      • 是的,确实如此,这两种方法都必须处理异常。如果你有List&lt;A&gt;,并不意味着你不能在里面放B
      猜你喜欢
      • 1970-01-01
      • 2012-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-17
      • 2022-01-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多