Java 为什么多态性不';不能以同样的方式处理泛型集合和普通数组吗?

Java 为什么多态性不';不能以同样的方式处理泛型集合和普通数组吗?,java,generics,collections,polymorphism,Java,Generics,Collections,Polymorphism,假设类狗扩展了类动物: 不允许使用此多态语句的原因: List<Animal> myList = new ArrayList<Dog>(); 那很有趣。我不能告诉你答案,但如果你想把狗的名单列入动物名单,这是可行的: List<Animal> myList = new ArrayList<Animal>(); myList.addAll(new ArrayList<Dog>()); List myList=new ArrayList

假设类狗扩展了类动物: 不允许使用此多态语句的原因:

List<Animal> myList = new ArrayList<Dog>();

那很有趣。我不能告诉你答案,但如果你想把狗的名单列入动物名单,这是可行的:

List<Animal> myList = new ArrayList<Animal>();
myList.addAll(new ArrayList<Dog>());
List myList=new ArrayList();
addAll(新的ArrayList());

对集合版本进行编码以便编译的方法是:

List<? extends Animal> myList = new ArrayList<Dog>();
列表
数组在两个重要方面不同于泛型类型。首先,数组是协变的。
这个听起来很吓人的词的意思很简单,如果Sub是Super的一个子类型,那么
数组类型Sub[]是Super[]的子类型。相比之下,泛型是不变的:例如
任何两种不同的类型Type1和Type2,List既不是子类型也不是
列表的超类型

[…]数组和泛型之间的第二个主要区别是数组是 具体化[JLS,4.7]。这意味着数组知道并执行其元素类型 运行时

相反,泛型是通过擦除实现的 [JLS,4.6]。这意味着它们仅在编译时强制执行其类型约束 并在运行时丢弃(或擦除)其元素类型信息。擦除是 什么允许泛型类型与不使用 仿制药(项目23)。 由于这些基本差异,数组和泛型不能混合使用 好。例如,创建泛型类型(参数化数组)的数组是非法的 类型或类型参数。这些数组创建表达式都不合法:new 列表[],新列表[],新E[]。所有这些都将导致创建通用数组 编译时出错。[..]

Prentice Hall-有效的Java第二版;
这是不可能的,因为在这种情况下,你可以把猫变成狗:

private void example() {
    List<Animal> dogs = new ArrayList<Dog>();
    addCat(dogs);
    // oops, cat in dogs here
}

private void addCat(List<Animal> animals) {
    animals.add(new Cat());
}
private void示例(){
List dogs=new ArrayList();
爱德猫(狗);
//哎呀,这里有猫捉狗
}
私人宠物猫(列出动物){
添加(新的Cat());
}
另一方面

List<? extends Animal> myList = new ArrayList<Dog>();

List最终的答案是这样,因为Java就是这样指定的。更准确地说,因为这是Java规范发展的方式*

我们不能说java设计者的实际想法是什么,但是请考虑一下:

List<Animal> myList = new ArrayList<Dog>();
myList.add(new Cat());   // compilation error
此处将抛出的运行时错误为。这可能会在分配给任何非原语数组时抛出

有人可能认为Java对数组类型的处理是错误的。。。因为上面的例子

*请注意,Java数组的类型在Java1.0之前就已经指定了,但泛型类型仅在Java1.5中添加。Java语言有一个向后兼容的超高元需求;i、 语言扩展不应该破坏旧代码。除其他外,这意味着不可能修复历史错误,例如数组类型的工作方式。(假设这是一个错误被接受…)


在泛型类型方面,类型擦除des不能解释编译错误。由于使用非擦除泛型类型进行编译类型检查,编译错误实际上正在发生


事实上,您可以通过使用取消选中的typecast(忽略警告)来破坏编译错误,并最终导致
ArrayList
在运行时实际包含
Cat
对象。(这是类型擦除的结果!)但请注意,使用未经检查的转换对编译错误进行的颠覆可能会在意外的地方导致运行时错误。。。如果你弄错了。这就是为什么它是个坏主意。

在泛型之前的日子里,编写一个可以对任意类型的数组进行排序的例程需要能够(1)以协变方式创建只读数组,并以与类型无关的方式交换或重新排列元素,或者(2)以可安全读取的协变方式创建读写数组,并使用以前从同一数组读取的内容安全写入,或者(3)让数组提供一些与类型无关的元素比较方法。如果从一开始就在语言中包含协变和逆变泛型接口,那么第一种方法可能是最好的,因为它可以避免在运行时执行类型检查以及此类类型检查失败的可能性。尽管如此,由于这种泛型支持不存在,因此除了基类型的数组之外,派生类型数组不能被合理地转换为其他类型。

原因在于Java实现泛型的方式

阵列示例

使用数组可以做到这一点(正如其他人所解释的,数组是协变的)

但是,如果你尝试这样做会发生什么

Number[0] = 3.14; //attempt of heap pollution
这最后一行可以很好地编译,但是如果您运行这段代码,您可能会得到一个
ArrayStoreException
。因为您试图将一个double放入整数数组中(不管通过数字引用访问)

这意味着您可以愚弄编译器,但不能愚弄运行时类型系统。这是因为数组是我们称之为可恢复类型的。这意味着在运行时Java知道该数组实际上被实例化为一个整数数组,而该数组恰好是通过类型为
Number[]
的引用访问的

所以,正如您所看到的,一件事是对象的实际类型,另一件事是您用来访问它的引用的类型,对吗

Java泛型的问题

现在,Java泛型类型的问题是类型信息被编译器丢弃,并且在运行时不可用。这个过程叫做。在Java中实现这样的泛型有很好的理由,但这是一个很长的故事,它与二进制有关
private void addCat(List<? extends Animal> animals) {
    animals.add(null);      // it's ok
    animals.add(new Cat()); // compilation error here
}
List<Animal> myList = new ArrayList<Dog>();
myList.add(new Cat());   // compilation error
Animal[] x = new Dog[3];
x[0] = new Cat();        // runtime error
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
Number[0] = 3.14; //attempt of heap pollution
List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);

List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap polution
static long sum(Number[] numbers) {
   long summation = 0;
   for(Number number : numbers) {
      summation += number.longValue();
   }
   return summation;
}
Integer[] myInts = {1,2,3,4,5};
Long[] myLongs = {1L, 2L, 3L, 4L, 5L};
Double[] myDoubles = {1.0, 2.0, 3.0, 4.0, 5.0};

System.out.println(sum(myInts));
System.out.println(sum(myLongs));
System.out.println(sum(myDoubles));
static long sum(List<Number> numbers) {
   long summation = 0;
   for(Number number : numbers) {
      summation += number.longValue();
   }
   return summation;
}
List<Integer> myInts = asList(1,2,3,4,5);
List<Long> myLongs = asList(1L, 2L, 3L, 4L, 5L);
List<Double> myDoubles = asList(1.0, 2.0, 3.0, 4.0, 5.0);

System.out.println(sum(myInts)); //compiler error
System.out.println(sum(myLongs)); //compiler error
System.out.println(sum(myDoubles)); //compiler error
List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>()
List<? extends Number> myNums = new ArrayList<Double>()
Number n = myNums.get(0); 
myNumst.add(45L); //compiler error
List<Object> myObjs = new List<Object();
myObjs.add("Luke");
myObjs.add("Obi-wan");

List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);
Number myNum = myNums.get(0); //compiler-error
public static void copy(List<? extends Number> source, List<? super Number> destiny) {
    for(Number number : source) {
        destiny.add(number);
    }
}
List<Integer> myInts = asList(1,2,3,4);
List<Double> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();

copy(myInts, myObjs);
copy(myDoubles, myObjs);