Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are arrays covariant but generics are invariant?

From Effective Java by Joshua Bloch,

  1. Arrays differ from generic type in two important ways. First arrays are covariant. Generics are invariant.
  2. Covariant simply means if X is subtype of Y then X[] will also be sub type of Y[]. Arrays are covariant As string is subtype of Object So

    String[] is subtype of Object[]

    Invariant simply means irrespective of X being subtype of Y or not ,

     List<X> will not be subType of List<Y>.
    

My question is why the decision to make arrays covariant in Java? There are other SO posts such as Why are Arrays invariant, but Lists covariant?, but they seem to be focussed on Scala and I am not able to follow.

like image 917
eagertoLearn Avatar asked Oct 06 '22 10:10

eagertoLearn


People also ask

What does it mean that arrays are covariant?

Object arrays are covariant, which means that just as Integer is a subclass of Number , Integer[] is a subclass of Number[] .

Why are arrays invariant?

Arrays in Kotlin are invariant, which means that an array of a specific type cannot be assigned to an array of its parent type. It is not possible to assign Array<Integer> to Array<Any> . This provides implicit type safety and prevents possible runtime errors in the application.

Are generics invariant?

Generics. With generic types, Java has no way of knowing at runtime the type information of the type parameters, due to type erasure. Therefore, it cannot protect against heap pollution at runtime. As such, generics are invariant.

Why is Java array covariant?

Arrays are said to be covariant which basically means that, given the subtyping rules of Java, an array of type T[] may contain elements of type T or any subtype of T . For instance: Number[] numbers = newNumber[3]; numbers[0] = newInteger(10); numbers[1] = newDouble(3.14); numbers[2] = newByte(0);


2 Answers

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);
like image 183
Paul Bellora Avatar answered Oct 07 '22 23:10

Paul Bellora


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
like image 35
Katona Avatar answered Oct 08 '22 00:10

Katona