List<Double> 是 List< 的子类型吗? extends Number> 为什么?

Is List<Double> a subtype of List<? extends Number> and why?

这是我所知道的:

  1. DoubleNumber 的子类型,而 List<Double> 不是 List<Number>.
  2. 的子类型
  3. List<Dog> 不是 List<Animal> 的子类型,因为您可以将 Cat 添加到 List<Animal>,但不能使用 List<Dog>
  4. List<? extends Number>表示这个列表可以存放Number类型的变量和Number子类型的变量。 List<Double>表示这个列表可以存储Double类型的变量。

如果以上内容有误,请指正 List<Double>List<? extends Number> 的子类型吗?

你所有的项目都是正确的。

  1. Double is a subtype of Number and List<Double> is not a subtype of List<Number>.

  2. List<Dog> is not a subtype of List<Animal> because you can add Cat to List<Animal> but you can't do that with List<Dog>.

没错。泛型不是协变的(但数组是!)。这是一些后续阅读:Why are arrays covariant but generics are invariant?

  1. List<? extends Number> means this list can store variables of type Number and variables of subtype of Number. List<Double> means this list can store variables of type Double.

这是事实,但 List<Number>List<? extends Number> 之间有一个重要的区别。您可以将 List<? extends Number> 视为特定 Number-子类型(即 List<Double>List<Integer>List<Long>、...之一)的列表,并且List<Number> 作为可能包含 DoubleInteger、...

的列表

最后一个问题:

Is List<Double> a subtype of List<? extends Number>...

是的,你可以有例如

List<Double> doubles = new ArrayList<>();
List<? extends Number> numbers = doubles;

... and why?

这就是定义子类型的方式。

至于动机,假设您有一个接受数字列表的方法。如果您让参数具有 List<Number> 类型,您将无法将 List<Double> 传递给它。 (问题中的第二项解释了原因!)相反,您可以让参数的类型为 List<? extends Number>。因为 List<Double>List<? extends Number> 的子类型,所以它会成功。

在运行时,List<T>List<U> 等同于 List (1)。

然而,这将随着值类型的引入而改变(预计将在 JDK 9 或 JDK 10 版本中实现,不早于 2016 年年中)。 List<T> 将不再与 List<U> 相同,因为 Brian Goetz 在这里解释了众多限制:http://cr.openjdk.java.net/~briangoetz/valhalla/specialization.html

(1) - TU 类型在前面的语句中是不同的

它帮助我将泛型视为约束或契约,而不是具有子类型的类型。 所以变量 List<? extends Number> var 表示:var 是一些未知类型的列表 ?,它被限制为 Number.

的子类型
List<Number> listN;
List<Double> listD;
List<? extends Number> listX;
...
Number n1 = ...;
Double d1 = ...;
...
listN.add(n1); // OK n1 is a Number
listN.add(d1); // OK d1 is a Double, which is a Number
listD.add(n1); // compile error, n1 is not a Double
listD.add(d1); // OK
listX.add(n1); // compile error, because the exact type of list is not known! (prevents putting a Dog in a Cat list)
listX.add(d1); // compile error, same cause

所以,当您甚至不能将数字放入 List<? extends Number> 时,这样一个列表的目的是什么?它允许您使用确切类型与手头任务无关的列表:

// instead of several exactly typed methods...
int count(List<Number> numberList) {...}
int count(List<Object> objectList) {...}
// ...etc. you can have one with a degree of freedom:
int count(List<?> anyList) {...} // don't need to know the exact type of list

// instead of this...
Number sum(List<Number> numberList) {...}
Number sum(List<Double> doubleList) {...}
Number sum(List<Integer> integerList){...}
// you can do this, with a little less freedom in the ?
Number sum(List<? extends Number> list) {
  // the only thing we need to know about the list's type is that it is some number type
  ...
  Number ni = list.get(i);
  ...
}

使用通配符 ? extends X 允许将严格的合同放宽到较弱的条件。

使用命名类型参数,您可以在多个变量之间建立对允许类型的约束:

// works for any type T of list, whatever T is
// T is the same in the argument and in the return
<T> T pickObject(List<T> list, int index) {
  return list.get(index);
}

// works for any type T of list, if T is a Number type
// T is the same in the argument and in the return
<T extends Number> T pickNumber(List<T> list, int index) {
  return list.get(index);
}
...
List<Number> list;
Number n = pickNumber(list);

还有几点需要补充

  1. 在运行时 List<Double>List<? extends Number>List<?>List<Object> 都是相同的。根本不编译通用参数。泛型的所有魔力都是编译时的乐趣。这也意味着如果你有一个空的 List 你不知道通用参数是什么!

  2. 尽量不要将泛型参数想象成 "Subtype",泛型参数的真正含义是 "the class uses a generic parameter",因此在本例中为 "the list uses a Number"。一个很好的例子是 HashMap source,如果你看一下它的内部工作原理,它实际上存储了一个 Entry 的数组,并且所有条目都存储有键和值。当您查看泛型的更复杂用法时,您偶尔会看到这种用法。

    List的情况下,泛型参数意味着列表存储了该类型的对象,也可能该对象根本不存储泛型参数类型的对象!像这样:

    public class DummyIterator<O> implements Iterator<O>{
        public boolean hasNext() {
            return false;
        }
    
        public O next() {
            return null;
        }
    }
    
  3. List<? extends Number>到底是什么意思?好吧,对于大多数用途来说,它与 List<Number> 几乎相同。请记住,通过说 ? 你几乎是在说“我不关心这种情况下的类型”:

    List<Double> doubles = new ArrayList<Double>();
    List<? extends Number> numbers = doubles;
    numbers.add(new Double(1));  //COMPILE ERROR
    Number num = numbers.get(0);
    

    所以我们无法将 Double 添加到 <? extends Number>。但是对于这个例子:

    List<Double> doubles = new ArrayList<Double>();
    List<Number> numbers = doubles; //COMPILE ERROR
    numbers.add(new Integer(1));
    Number num = numbers.get(0);
    

    您不能将 List<Double> 分配给 List<Number>,这在您明确说明时是有意义的,列表仅使用数字类型

  4. 那么你应该在哪里使用??其实在任何地方你都可以说 "I don't care about the generic parameter" 例如:

    boolean equalListSizes(List<?> list1, List<?> list2) {
      return list1.size() == list2.size();
    }
    

    您只能在 不使用通用参数修改对象 的情况下使用 ? extends Number 类型的格式。例如:

      Number firstItem(List<? extends Number> list1) {
        return list1.get(0);
      }
    
  5. 不要使用 ?? extends Number 格式,而是尝试在 class / 方法上使用泛型,在大多数情况下,它会使您的代码更具可读性好吧!:

    <T extends Number> T firstItem(List<T> list1) {
      return list1.get(0);
    }
    

    Class:

    class Animal{}
    class Dog extends Animal{}
    class AnimalHouse<A extends Animal> {
        List<A> animalsInside = new ArrayList<A>(); 
        void enterHouse(A animal){
            animalsInside.add(A);
        }
    
        A leaveHouse() {
            return animalsInside.remove(0);
        }
    }
    AnimalHouse<Dog> ah = new AnimalHouse<Dog>();
    ah.enterHouse(new Dog());
    Dog rufus = ah.leaveHouse();
    
  6. 作为围绕泛型的额外思考,您还可以将方法参数化为 return 特定 class。一个很好的例子是 junit 中的 any() 方法和空列表集合:

    Dog rufus = Matchers.<Dog>any();
    List<Dog> dogs = Collections.<Dog>emptyList();
    

    此语法允许您指定对象的 return 类型。有时了解非常有用(使一些铸造变得多余)!