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

12
这是我所知道的:
  1. DoubleNumber 的子类型,而 List<Double> 不是 List<Number> 的子类型。
  2. List<Dog> 不是 List<Animal> 的子类型,因为你可以向 List<Animal> 中添加 Cat ,但不能添加到 List<Dog> 中。
  3. List<? extends Number> 表示此列表可以存储类型为 Number 或 Number 子类型的变量。 List<Double> 表示此列表可以存储类型为 Double 的变量。
如果上面任何一项有误,请纠正我。那么,List<Double>List<? extends Number> 的子类型吗?为什么?
答案是 “是”。原因是,List<? extends Number> 可以存储 Number 和 Number 子类型的变量,而 List<Double> 是这样一个类型,它只能存储 Double 类型的变量。因此,List<Double>List<? extends Number> 的子类型。

第三项是正确的,但是你不能将 DoubleInteger 都存储在相同的 List<? extends Number> 列表中。 List<Double>List<Integer> 都是 List<? extends Number>,但是 List<? extends Number> 不等于 List<Number> - aioobe
我认为很清楚。Dog 可能是继承自 Animal 的。 - aioobe
4个回答

11

您的所有项都是正确的。

  1. DoubleNumber 的子类型,而 List<Double> 不是 List<Number> 的子类型。

  2. List<Dog> 不是 List<Animal> 的子类型,因为您可以将 Cat 添加到 List<Animal> 中,但不能将其添加到 List<Dog> 中。

这是正确的。泛型不是协变的(但数组是!)。以下是一些后续阅读材料:为什么数组是协变的而泛型是不变的?

  1. List<? extends Number> 表示此列表可以存储类型为 NumberNumber 子类型的变量。而 List<Double> 表示此列表只能存储类型为 Double 的变量。

这是正确的,但是 List<Number>List<? extends Number> 之间有一个重要区别。您可以将 List<? extends Number> 视为特定的 Number 子类型列表(即其中一个:List<Double>List<Integer>List<Long>等),而将 List<Number> 视为可能包含混合元素的列表(例如 DoubleInteger 的组合)。

至于您最后的问题:

List<Double> 是否是 List<? extends Number> 的子类型...

是的,您可以拥有例如

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

... 为什么?

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

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


1
但是 List<? extends Number> 可以存储类型为 Int 的变量,而 List<Double> 则不能。我们知道子类型应该具有其超类型的所有函数,所以我感到困惑。 - kahofanfan
1
需要考虑的另一件事是: numbers.add(new Double(1.2));numbers.add(new Integer(1)); 不会编译通过,但是 Number num = numbers.get(0); 将返回扩展 Number 的某些内容。通常尽量避免使用 <? extends X>,你最好使用一个类或使用 <T> 风格。例如:public <T> void addToList(List<T> doubles, T value){ doubles.add(value); } - FuzzyJulz
1
@Chop,我认为方法重载与子类型化无关(至少在这个问题的背景下不是)。 - aioobe
1
@Random832 它还可以存储 List<Number>,您可以向其中添加 Integer 实例。 - Chop
1
@cHao 不,那不是真的。请参见4.10.24.5.1 - Radiodef
显示剩余12条评论

6

在运行时,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类型是不同的。


这绝不是已经解决的事情。一切仍然未确定。正如Brian所写的:“这是对Java语言拟议增强的非正式草图。” - aioobe
1
尽管如此,我仍然不明白你的回答与问题有什么关系。OP谈论的是子类型(由编译器静态处理),而你的回答涉及到运行时。 - aioobe
继承也存在于运行时。有不同的观点有什么问题吗? - Silviu Burcea
2
OP问他的三个项目是否正确,然后问*List<Double>List<? extends Number>的子类型吗?为什么?*我看不出你在回答中如何解决这些问题。(这不是观点不同的问题。) - aioobe
这次的向后兼容性我不是很确定...让我对此持有不同的意见,好吗? :) - Silviu Burcea
显示剩余2条评论

6
有几个要点需要补充。
  1. At runtime List<Double>, List<? extends Number>, List<?> and List<Object> are all identical. The generic parameter is not compiled at all. All the magic with generics is all compile time fun. This also means that if you have a List that is empty you have no idea what the generic parameter is!

  2. Try not to think of the generic parameter as a "Subtype", the generic parameter really means "the class uses a generic parameter" so in this case "the list uses a Number". A good example of this is the HashMap source, if you have a look into the inner workings of it, it is actually storing an array of Entry, and the entries all have keys and values stored on them. When you look at more complex uses of generics you occasionally see this sort of use.

    In the situation of a List the generic parameter means that the list stores that type of object, it could be that the object never stores an object of the type of the generic parameter at all! Like this:

    public class DummyIterator<O> implements Iterator<O>{
        public boolean hasNext() {
            return false;
        }
    
        public O next() {
            return null;
        }
    }
    
  3. What does List<? extends Number> actually mean? Well it is pretty much the same as List<Number> for most uses. Keep in mind though that by saying ? you are pretty much saying 'I don't care about the type' which shows itself in this situation:

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

    So we can't add a Double to a <? extends Number>. But for this example:

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

    You can't assign the List<Double> to a List<Number> which makes sense as you are specifically telling it, that lists use only Number types

  4. So where should you use a ?? well really anywhere you could say "I don't care about the generic parameter" so for instance:

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

    You would use the ? extends Number type of format only where you are not modifying the object using the generic parameter. so for instance:

      Number firstItem(List<? extends Number> list1) {
        return list1.get(0);
      }
    
  5. Instead of using the ? and ? extends Number formats try using generics on the class / method instead, in most cases it makes your code more readable as well!:

    <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. As a bonus thought around generics you can also parameterise methods to return a particular class. A good example of this is the any() method in junit and the empty list collection:

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

    This syntax allows you to specify the return type of an object. Sometimes quite useful to know (makes some casting redundant)!


你能详细解释一下你的第二点吗?1. "...所以在这种情况下,“列表使用数字”。" 这里的“使用”是什么意思?你是指“存储”吗?2. "...列表存储该类型的对象..." 和 "...可能是该对象从未存储过泛型参数类型的对象..." 这个句子太复杂了,我理解起来很困难(我不是母语人士)。 - kahofanfan
@kahofanfan:已编辑,希望这能为您澄清我的意思。 - FuzzyJulz
优雅的方法名 enterHouse()leaveHouse() - aliopi

3

它帮助我将泛型视为约束或合同,而不是具有子类型的类型。

因此,变量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);

所以不只是我在想这个。我正在考虑写这样的东西。谢谢。 - Chop

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接