在Java中的这个例子中无法理解通配符泛型?

时间:2013-10-24 22:18:56

标签: java generics arraylist

在阅读article时,我被困在这里。我从链接中粘贴了这个。我不明白为什么List<Number>List<? extends Number>不能在这里使用的原因。

public void doStuff( List<Integer> list ) {     
    list.add(1);
    // do stuff
    list.get(0);    
}

We can generalize this one step further by generalizing the generic parameter:

public void doStuff( List<? super Integer> list ) {     
    list.add(1);
    // do stuff
    list.get(0);    
}
  

有些读者可能会更直观地问为什么List&lt; Number&gt;不能在这里使用。的确,我们           可以尝试将该方法定义为采用List&lt; Number&gt;或列表&lt;?扩展数字&gt;,但第一个          定义将排除传入实际ArrayList&lt; Integer&gt;的可能性,而           第二个定义将禁止使用add()方法(因为有人可能会传递一个           ArrayList的&LT;浮法&GT; in,并且在调用doStuff之后会在Floats之间找到一个Integer。)

4 个答案:

答案 0 :(得分:5)

在普通的Java中,是的,IntegerNumber。但在泛型中,List<Integer>不是List<Number>

要了解原因,请尝试将List<Integer>分配给List<Number>,看看会发生什么:

List<Number> numberList = new ArrayList<Integer>();  // not allowed
// This would have been allowed, even though the argument is
// boxed to a `Double`, not a `Integer`.
numberList.add(8.6); 

但是<? extends Number>呢?这不会涵盖List<Integer>吗?是的,但参考文献丢失了有关Number的确切类型的信息。如果是这样的话怎么办?

List<? extends Number> numberList = new ArrayList<Integer>();  // allowed
numberList.add(8.6);  // disallowed

原因是List<? extends Number>可能是任何扩展Number的内容,例如List<BigDecimal>。因此,它必须禁止调用add方法(或该类中的任何方法,使用泛型类型参数作为该方法的参数)(null除外)以维护类型安全。

答案 1 :(得分:1)

Java可能有点令人困惑,因为工作中有一点双重标准。

首先,您必须考虑数组和集合都是引用类型,即它们的实例是其数据分配给堆内存并由引用指针指示的对象。

因此,数组和集合在工作中都有两种类型:对象本身的类型以及数组或集合中每个组件的类型。为了具体化,这是一个例子:

String[] strings = new String[] { "AA", "BB", "CC" };

创建的对象类型为String[],所有组件的类型为String

数组是协变,它允许JVM将对象类型和组件类型一起转换。这就是为什么这样的作业是有效的:

Object[] objects = strings;

对于数组,因为ObjectString的超类型,所以Object[]也是String[]的超类型。数组是协变的。

这不适用于非数组的引用类型,例如。集合。收藏品是不变的。因此,即使IntegerNumber的子类型,集合也是不变的,因此List<Integer>不是List<Number>的子类型。

答案 2 :(得分:0)

对于第一种情况,仅接受List<Number> 允许List(ArrayListLinkedList,...) > Number 元素,而不是任何其他类型的List(包括Integer)。该列表必须在调用函数中专门输入List<Number>(或ArrayList<Number>LinkedList<Number>或...)。换句话说,列表的类型是灵活的,但通用参数不是。

在第二种情况下,使用“是a”措辞,这是有道理的。 Integer“是”Number,但反之并非总是如此。由于示例中的代码假设函数中使用的所有值都是整数,因此必须对泛型进行限制,以防止传递任何不太具体的值Integer

答案 3 :(得分:-1)

我们需要检查两件事:

  1. 签名void doStuff(Foo<? super Bar> foo)
  2. 中的通配符是什么意思
  3. 通配符在方法体内的含义是什么
  4. Java只有一个非常简单的规则来决定Foo<A>Foo<B>之间的子类型关系: none是另一个的子类型。我们说泛型类型是不变的,即使有一个基本原理,因为Java设计者这样做,从你的角度来看,这是一个随意的决定。

    这是尖锐的括号让我们感到困惑,可怜的开发者:我们毫不犹豫地接受FooBarFooQoo没有任何关联;但出于某种原因,我们需要相信Foo<Qoo>Foo<Bar>。不,事实并非如此。

      

    无论A和B如何相互关联,X<A>X<B>都不是   相关。

         

    无论A和B如何相互关联,X<A>X<B>   没有关系。

         

    无论A和B如何相互关联,X<A>   和X<B>无关。

    一旦您确信上述情况,请观察以下片段:

    List<Double> doubles = ...;
    List<Integer> integers = ...;
    
    Number firstDouble = doubles.get(0);
    Number firstInteger = integers.get(0);
    

    在两个列表上调用get(0)为我们提供了一个与数字兼容的对象。我们可能希望将get()的调用放在像getFirstOfList(list)这样的方法中,但我们只是知道这样的方法不存在,因为它会接受两个完全不相关的类型。

    这是通配符发挥作用的地方!我们发现在get()List<Number>List<Integer>(等等)上调用List<Double>会返回与数字兼容的对象(即Number或其子类型),因此必须有一种方法可以在语言层面表达这一点。 Java设计者给了我们通配符,它​​的工作方式如下:当你声明

    public void doStuff(List<? extends Number> arg);
    

    它与声明以下无限列表具有相同的效果:

    public void doStuff(List<Number> arg);
    public void doStuff(List<Integer> arg);
    public void doStuff(List<Double> arg);
    public void doStuff(List<Float> arg);
    public void doStuff(List<BigDecimal> arg);
    ...
    

    如果没有通配符的设备,你必须为每个支持的列表类型编写一个方法(在Java中这是非法的,但这是另一个故事)。

    我在我的示例中使用的通配符有一个 upper 绑定,由extends关键字标识。相反,您粘贴的代码段在方法签名(super)中采用了 lower 有界通配符。实际上它可能包含一些错误,例如:

    1. 您无法将List<Integer>传递给doStuff()
    2. 您只能从Object
    3. 获得list.get(index)

      所以我只会告诉你签名

      void doStuff(List<? super Number> arg);
      

      代表有限列表:

      void doStuff(List<Number> arg);
      void doStuff(List<Object> arg);
      

      并且您可以将Number中的任何List<? super Number>放在get()中,但您只能Object {{1}}。