Why are arrays covariant but generics are invariant?

前端 未结 9 1258
没有蜡笔的小新
没有蜡笔的小新 2020-11-22 00:44

From Effective Java by Joshua Bloch,

  1. Arrays differ from generic type in two important ways. First arrays are covariant. Generics are invariant.
  2. Cov

相关标签:
9条回答
  • 2020-11-22 01:21

    The reason is that every array knows its element type during runtime, while generic collection doesn't because of type erasure.

    For example:

    String[] strings = new String[2];
    Object[] objects = strings;  // valid, String[] is Object[]
    objects[0] = 12; // error, would cause java.lang.ArrayStoreException: java.lang.Integer during runtime
    

    If this was allowed with generic collections:

    List<String> strings = new ArrayList<String>();
    List<Object> objects = strings;  // let's say it is valid
    objects.add(12);  // invalid, Integer should not be put into List<String> but there is no information during runtime to catch this
    

    But this would cause problems later when someone would try to access the list:

    String first = strings.get(0); // would cause ClassCastException, trying to assign 12 to String
    
    0 讨论(0)
  • 2020-11-22 01:22

    Arrays are covariant for at least two reasons:

    • It is useful for collections that hold information which will never change to be covariant. For a collection of T to be covariant, its backing store must also be covariant. While one could design an immutable T collection which did not use a T[] as its backing store (e.g. using a tree or linked list), such a collection would be unlikely to perform as well as one backed by an array. One might argue that a better way to provide for covariant immutable collections would have been to define a "covariant immutable array" type they could use a backing store, but simply allowing array covariance was probably easier.

    • Arrays will frequently be mutated by code which doesn't know what type of thing is going to be in them, but won't put into the array anything which wasn't read out of that same array. A prime example of this is sorting code. Conceptually it might have been possible for array types to include methods to swap or permute elements (such methods could be equally applicable to any array type), or define an "array manipulator" object which hold a reference to an array and one or more things that had been read from it, and could include methods to store previously-read items into the array from which they had come. If arrays were not covariant, user code would not be able to define such a type, but the runtime could have included some specialized methods.

    The fact that arrays are covariant may be viewed as an ugly hack, but in most cases it facilitates the creation of working code.

    0 讨论(0)
  • 2020-11-22 01:28

    Via wikipedia:

    Early versions of Java and C# did not include generics (a.k.a. parametric polymorphism).

    In such a setting, making arrays invariant rules out useful polymorphic programs. For example, consider writing a function to shuffle an array, or a function that tests two arrays for equality using the Object.equals method on the elements. The implementation does not depend on the exact type of element stored in the array, so it should be possible to write a single function that works on all types of arrays. It is easy to implement functions of type

    boolean equalArrays (Object[] a1, Object[] a2);
    void shuffleArray(Object[] a);
    

    However, if array types were treated as invariant, it would only be possible to call these functions on an array of exactly the type Object[]. One could not, for example, shuffle an array of strings.

    Therefore, both Java and C# treat array types covariantly. For instance, in C# string[] is a subtype of object[], and in Java String[] is a subtype of Object[].

    This answers the question "Why are arrays covariant?", or more accurately, "Why were arrays made covariant at the time?"

    When generics were introduced, they were purposefully not made covariant for reasons pointed out in this answer by Jon Skeet:

    No, a List<Dog> is not a List<Animal>. Consider what you can do with a List<Animal> - you can add any animal to it... including a cat. Now, can you logically add a cat to a litter of puppies? Absolutely not.

    // Illegal code - because otherwise life would be Bad
    List<Dog> dogs = new List<Dog>();
    List<Animal> animals = dogs; // Awooga awooga
    animals.add(new Cat());
    Dog dog = dogs.get(0); // This should be safe, right?
    

    Suddenly you have a very confused cat.

    The original motivation for making arrays covariant described in the wikipedia article didn't apply to generics because wildcards made the expression of covariance (and contravariance) possible, for example:

    boolean equalLists(List<?> l1, List<?> l2);
    void shuffleList(List<?> l);
    
    0 讨论(0)
提交回复
热议问题