【问题标题】:Java Collections: List<Animal> tiger = new ArrayList<Tiger>() WRONGJava 集合:List<Animal> tiger = new ArrayList<Tiger>() 错误
【发布时间】:2012-03-28 04:12:34
【问题描述】:

Tiger 类是 Animal 类的扩展。

当我声明时:List&lt;Animal&gt; tiger = new ArrayList&lt;Tiger&gt;();。我会在编译时出错。

但是,我认为这条线适用于多态性。谁能帮我解释一下。

【问题讨论】:

  • 如果您/您的 API 设计确定“老虎”ArrayList 将只包含 Tigers,为什么不将其声明为 List&lt;Tiger&gt; tiger =... 另外,Animal 和 Tiger 不是多态性的一个例子。它是继承。
  • 这是有史以来最常见的 Java 泛型问题。

标签: java generics collections


【解决方案1】:

你做不到

List<Animal> tiger = new ArrayList<Tiger>();

在java中。左侧的泛型类型必须与右侧的泛型类型完全相等(或者可能不必相等,如果游戏中有通配符 - ? extends T? super T)。

如果有可能,那么就不可能将新的Lion 添加到声明为Animals 的列表中——这没有任何意义。

你可以做的是:

List<Animal> tigers = new ArrayList<Animal>();
tigers.add(new Tiger());

Animals 的所有家族,包括Tigers)

或:

List<? extends Animal> tigers = new ArrayList<Tiger>();
tigers.add(new Tiger()); // Adding is immpossible now - list can be read only now! 

(仅限Animal 的子类)- 现在只能读取列表!

【讨论】:

    【解决方案2】:

    List&lt;Animal&gt; 可以让您添加一只可爱的小狗。然后ArrayList&lt;Tiger&gt; 的老虎会吃。

    从多态的角度来说,你会有

    List<Tiger> tigers = new ArrayList<Tiger>();
    

    如果您愿意,这将允许您替换使用List&lt;Tiger&gt; 的任何实现,依赖并使用接口定义的功能。您要做的不是多态性,它只是一种不安全的转换(特别是对于上述小狗),并且由于上述原因而无法工作。

    【讨论】:

      【解决方案3】:

      原因在于 Java 如何实现泛型。我发现解释它的最好方法是首先使用数组。

      数组示例

      使用数组,您可以这样做:

      Integer[] myInts = {1,2,3,4};
      Number[] myNumber = myInts;
      

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

      Number[0] = 3.14; //attempt of heap pollution
      

      最后一行可以正常编译,但如果你运行这段代码,你会得到一个ArrayStoreException

      这意味着你可以欺骗编译器,但不能欺骗运行时类型系统。之所以如此,是因为数组就是我们所说的reifiable types。这意味着在运行时 Java 知道这个数组实际上被实例化为一个整数数组,它恰好是通过 Number[] 类型的引用访问的。

      所以,如您所见,一件事是对象的真实类型,另一件事是您用来访问它的引用的类型,对吧?

      Java 泛型的问题

      现在,Java 泛型类型的问题是类型信息被编译器丢弃,并且在运行时不可用。这个过程称为type erasure。在 Java 中实现这样的泛型是有充分理由的,但这是一个很长的故事,它与与预先存在的代码的二进制兼容性有关。

      但这里重要的一点是,由于在运行时没有类型信息,因此无法确保我们没有提交堆污染。

      例如,

      List<Integer> myInts = new ArrayList<Integer>();
      myInts.add(1);
      myInts.add(2);
      
      List<Number> myNums = myInts;
      myNums.add(3.14); //heap polution
      

      如果 Java 编译器在编译时没有阻止你这样做,那么运行时类型系统也不能阻止你,因为在运行时没有办法确定这个列表应该是一个整数列表. Java 运行时允许你将任何你想要的东西放入这个列表中,当它应该只包含整数时,因为当它被创建时,它被声明为一个整数列表。

      因此,Java 的设计者确保您不能欺骗编译器。如果你不能欺骗编译器(就像我们可以用数组做的那样),你也不能欺骗运行时类型系统。

      因此,我们说泛型类型是不可具体化的

      显然,这会妨碍多态性,正如所指出的那样。解决方案是学习使用 Java 泛型的两个强大特性,即协变和逆变。

      协方差

      通过协方差,您可以从结构中读取项目,但不能向其中写入任何内容。所有这些都是有效的声明。

      List<? extends Number> myNums = new ArrayList<Integer>();
      List<? extends Number> myNums = new ArrayList<Float>()
      List<? extends Number> myNums = new ArrayList<Double>()
      

      你可以从myNums阅读:

      Number n = myNums.get(0);
      

      因为您可以确定无论实际列表包含什么,它都可以向上转换为一个数字(毕竟任何扩展 Number 的东西都是一个数字,对吧?)

      但是,您不能将任何内容放入协变结构中。

      myNumst.add(45L);
      

      这是不允许的,因为 Java 不能保证真实对象的实际类型是什么。它可以是任何扩展 Number 的东西,但编译器不能确定。所以你可以读,但不能写。

      逆变

      通过逆变,你可以做相反的事情。您可以将事物放入通用结构中,但不能从中读出。

      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
      

      如您所见,如果编译器允许您编写此行,您将在运行时收到 ClassCastException。

      Get/Put 原则

      因此,当您只打算将泛型值从结构中取出时使用协变,当您只打算将泛型值放入结构中时使用逆变器,当您打算同时执行这两种操作时使用精确的泛型类型。

      最好的例子是下面的例子,它将任何类型的数字从一个列表复制到另一个列表。

      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<Integer> myDoubles = asList(3.14, 6.28);
      List<Object> myObjs = new ArrayList<Object>();
      
      copy(myInts, myObjs);
      copy(myDoubles, myObjs);
      

      【讨论】:

      • 很棒的帖子 :) 谢谢 :) 很多我以前不知道的有用信息 :D
      【解决方案4】:

      我同意这令人困惑。如果允许这种类型的声明,可能会出现以下问题:

      List<Tiger> tigers = new ArrayList<Tiger>();  // This is allowed.
      List<Animal> animals = tigers;  // This isn't allowed.
      tigers.add(new Lion());  // This puts a Lion in tigers!
      

      【讨论】:

        【解决方案5】:

        哦。是的,你说得对。您的代码在多态思维中是正确的。但是,看看我的代码,当我只是一个新手时,我使用了很长时间。 你会明白为什么要感谢Collection

        class Animal{   
        }
        class Tiger extends Animal{
        
        }
        public class Test {
        
            public static void main (String[] args){
        
                List<Animal> animal = new ArrayList<Animal>();  //obvious
                List<Tiger> tiger = new ArrayList<Tiger>();     //obvious
        
                List<Animal> tigerList = new ArrayList<Tiger>();  //error at COMPILE-TIME
                Animal[] tigerArray = new Tiger[2];     //like above but no error but....
        
                Animal tmpAnimal = new Animal();
                /*
                 * will meet RUN-TIME error at below line when use Array
                 * but Collections can prevent this before at COMPILE-TIME
                 */
                tigerArray[0] = tmpAnimal;   //Oh NOOOO. RUN-TIME EXCEPTION
        
                /*
                 * Below examples WRONG for both Collection and Array
                 * Because here is Polymorphism problem. I just want to make more clearer
                 */
                List<Tiger> animalList = new ArrayList<Animal>();
                Tiger[] animalArray = new Animal[2];        
            }
        
        }
        

        正如你看到我上面的代码,Collections 在阻止你使用 List&lt;Animal&gt; tigerList = new ArrayList&lt;Tiger&gt;(); 时是如此“智能”

        你应该想象如果有人使用:tigerList.add(a Lion, a Cat,......); --->ERROR。

        所以,总结一下,这里是不同的:

        阵列:在运行时检查。你会感觉更舒服,但很危险

        集合:在编译时检查。你会生气,因为它 注意错误。但是,您将在运行时防止错误。 !!!!

        也许下面的帖子已经结束了你的问题。但我建议您使用通配符,例如:

        List&lt;? extends Animal&gt; tigerList = new ArrayList&lt;Tiger&gt;();

        是的。您可能会看到这条线背后的想法。但是,最有趣的是:它会阻止您更改列表。在这种情况下,add 方法。 例如:

        tigerList.add(TIGER); ERROR

        是的。它也会阻止你添加老虎:)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-06-26
          • 2010-10-10
          • 2010-09-11
          • 1970-01-01
          • 1970-01-01
          • 2011-02-25
          • 1970-01-01
          相关资源
          最近更新 更多