Why are arrays Covariant and generic are invariant?

Arrays in Java are covariants, that is, it is perfectly cool to do something like:

Number[] meuArray = new Integer[10];

Already generic types are invariant. The line below does not compile:

List<Number> minhaLista = new ArrayList<Integer>();

Compiler output:

error: incompatible types: ArrayList<Integer> cannot be converted to List<Number>
        List<Number> minhaLista = new ArrayList<Integer>();
                                  ^

The question is, why did you decide to implement Covariant arrays?


Original question: SOen-Why are arrays covariant but generics are invariant?

Author: Comunidade, 2016-08-22

1 answers

Covariant Arrays are considered a language flaw. Ideally they should be invariant as well.

The main reason they were conceived as covariants was a bad attempt to allow the implementation of generic sets of objects, especially so that methods that worked with arrays of objects could work with arrays of any type of object. This concept is actually a gambiarra, but it was a gambiarra that made it possible to creating some code that worked in a minimally generic way.

Codes that took advantage of the fact of covariance of arrays are codes that write elements in the same array from which they are read, such as codes that sort elements of an array, or shuffle it, or duplicate them, etc. A simple example:

public static void embaralhar(Object[] array) {
    for (int i = array.length - 1; i > 0; i--) {
        int j = (int) (Math.random() * (i + 1));
        Object temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}

Note that since the type of the parameter is Object[], then it can be used to String[], Thread[], Cachorro[], based on the reasoning that " if String is subclass of Object, then String[] is subclass of Object[]", that is, the code works because of covariance. The type of the variable temp is Object, but represents an object obtained from within the array itself, and which can therefore be safely put back into it again. Note that in case you try to use covariance to pollute an array with something whose type is incompatible (and therefore cannot have come from it itself), such as appending a Gato in an instance of Cachorro[] represented by a variable of type Object[], you will take a ArrayStoreException:

Cachorro[] array = new Cachorro[3];
Object[] mesmoArray = array;
mesmoArray[0] = new Gato(); // Estoura uma ArrayStoreException

This then dates back to the time of Java 1.0. In fact, it predates even Java 1.0, as it emerged in the early stages of Java 1.0 development when it was still called Oak. At that time, one did not have the generic types that exist today (which came up with Java 5). We also did not have the collections framework developed as today, and everything that referred to object collections were treated as arrays (this concept inherited from C and C++). The maximum that was had at the time were the classes Vector, Hashtable and Stack, which suffer from very serious modeling problems and were intended more to serve as an example or a twig breaker than to do decent data modeling.

C++ has templates, which could serve as inspiration. However, the templates were not well viewed and did not want to imitate them in Java. The reason for this is that the C++ compiler creates a version of the class or method for each template combination found, which greatly increases the size of the executable code produced. Since the idea of Java was to be able to run even on devices with little memory and low computational power, this alternative was prohibitive. And also the C++ templates were invariant, and therefore a similar template would not serve to solve many of the problems faced, such as sorting lists of elements without needing to know the type. Therefore, the consensus at the time was that Java did not need it.

It took a good amount of time (and a good dose of practical experience and abuse of casts) to abandon such a consensus and form some idea that would allow the introduction of generic types without increasing the size of the executable code produced and that had good flexibility in terms of covariance and contravariance. From this came the (hated) type-erasure and the super and the extends that they appear in some generic types. super and extends provide contravariance and covariance respectively.

The type-erasure ensures that in the JVM generic types disappear, making them just a trick introduced by the compiler. Basically, in Java with generics, the compiler adds Under the cloths the casts that would be needed in Java without generics, which is a completely different approach than the one taken by C++templates. However, this solution based on type-erasure has created some problems, as generic types cannot be protected from data pollution in the same way arrays do (which have ArrayStoreException for this). In addition, the implementation of arrays could not be compatible with generics (which is why mixing arrays with generics is difficult).

An example of data pollution (heap pollution):

List<Cachorro> cachorros = new ArrayList<>();

// O compilador dá uma warning unchecked.
List<Object> objetos = (List<Object>) cachorros;

// Poluição de dados aqui. Um gato é introduzido na lista de cachorros!
objetos.add(new Gato());

// Vai dar ClassCastException, mas não há nenhum cast neste código.
// Na verdade, o compilador introduziu um cast por debaixo dos panos.
Cachorro c = cachorros.get(0);

If Java had generic types properly implemented from the start, arrays would not need to be invariant and type-erasure would not have been required. However, much of the knowledge needed for generics to actually be implemented and to demonstrate which implementation strategies would work or not, was only developed thanks to the experience accumulated after a large number of Java developers suffered from this problem and sought various solutions for themselves. Besides in addition, generics are a significantly complicated little monster and implementing them from the beginning would have made it very difficult and delayed the release of Java as a programming language, so just having Covariant arrays turned out to be the simplest output.

 10
Author: Victor Stafusa, 2016-08-22 19:48:18