“ArrayList <a>&#34; and &#34;ArrayList<!--? extends A-->&#34;?</a>之间的实际区别是什么?

时间:2012-04-14 10:00:03

标签: java generics

我查看了问题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)?

如果这是正确的,那么是否存在两个表达式的其他场景 有效的不同?

谢谢,

6 个答案:

答案 0 :(得分:8)

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

List<Number> list;

这意味着分配给此变量或字段的内容需要Number并输出Number,因此您始终知道会发生什么。 Integer扩展Integer后,Number可以添加到此列表中。但是,您无法将ArrayList<Long>分配给此列表。

但请考虑这种情况:

List<? extends Number> list;

这个说:嘿,这是一个延伸Number的东西的清单,但没有人知道究竟是什么。这是什么意思?这意味着您可以将ArrayList<Long>分配给此列表,这在第一种情况下是不可能的。你仍然知道无论这个列表输出Number,你都不能在其中放置 Integer

还有一个相反的情况:

List<? super Number> list;

通过打印你说:那是Number或其超类的列表。这就是反之亦然的一切。该列表现在可以引用ArrayList<Object>ArrayList<Number>。现在我们不知道这个列表将输出。它会是Number吗?它会是Object吗?但现在我们知道我们可以在此列表中添加 Number以及Number的任何子类,例如IntegerLong

顺便说一下,有一条规则说明生产者扩展,消费者超级(简称PECS)。如果您需要列表来输出值,那么它是生产者,这是第二种情况。如果您需要列表接受值,那么它是消费者,这是第三种情况。如果你需要两者,不要使用通配符(这是第一种情况)。

我希望这可以解决问题。

答案 1 :(得分: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 {}
}

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

答案 2 :(得分:1)

ArrayList<A>表示特定的A类,其中ArrayList<? extends A>表示A类或任何使用A(Sub的A类)的类,这使得它更通用

答案 3 :(得分:1)

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

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

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

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

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