¿Por qué las matrices son covariantes pero los genéricos son invariantes?

Resuelto eagertoLearn asked hace 11 años • 8 respuestas

De Java eficaz de Joshua Bloch,

  1. Las matrices se diferencian del tipo genérico en dos aspectos importantes. Las primeras matrices son covariantes. Los genéricos son invariantes.
  2. Covariante simplemente significa que si X es un subtipo de Y, entonces X[] también será un subtipo de Y[]. Las matrices son covariantes ya que la cadena es un subtipo de objeto.

    String[] is subtype of Object[]

    Invariante simplemente significa independientemente de si X es un subtipo de Y o no,

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

Mi pregunta es ¿por qué la decisión de hacer que las matrices sean covariantes en Java? Hay otras publicaciones SO, como ¿ Por qué las matrices son invariantes y las listas covariantes? , pero parecen estar centrados en Scala y no puedo seguirlos.

eagertoLearn avatar Sep 07 '13 04:09 eagertoLearn
Aceptado

Vía wikipedia :

Las primeras versiones de Java y C# no incluían genéricos (también conocido como polimorfismo paramétrico).

En tal entorno, hacer que las matrices sean invariantes descarta programas polimórficos útiles. Por ejemplo, considere escribir una función para mezclar una matriz, o una función que pruebe la igualdad de dos matrices utilizando el Object.equalsmétodo de los elementos. La implementación no depende del tipo exacto de elemento almacenado en la matriz, por lo que debería ser posible escribir una única función que funcione en todos los tipos de matrices. Es fácil implementar funciones de tipo

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

Sin embargo, si los tipos de matriz se trataran como invariantes, solo sería posible llamar a estas funciones en una matriz de exactamente el tipo Object[]. Por ejemplo, no se podría mezclar una serie de cadenas.

Por lo tanto, tanto Java como C# tratan los tipos de matrices de forma covariante. Por ejemplo, en C# string[]es un subtipo de object[]y en Java String[]es un subtipo de Object[].

Esto responde a la pregunta "¿Por qué las matrices son covariantes?", o más exactamente, "¿Por qué las matrices se hicieron covariantes en ese momento ?"

Cuando se introdujeron los genéricos, intencionalmente no se hicieron covariantes por las razones señaladas en esta respuesta de Jon Skeet :

No, a List<Dog>no es a List<Animal>. Considere lo que puede hacer con un List<Animal>... puede agregarle cualquier animal... incluido un gato. Ahora bien, ¿se puede lógicamente añadir un gato a una camada de cachorros? Absolutamente no.

// 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?

De repente tienes un gato muy confundido.

La motivación original para hacer covariantes las matrices descrita en el artículo de Wikipedia no se aplicaba a los genéricos porque los comodines hacían posible la expresión de covarianza (y contravarianza), por ejemplo:

boolean equalLists(List<?> l1, List<?> l2);
void shuffleList(List<?> l);
Paul Bellora avatar Sep 06 '2013 21:09 Paul Bellora

La razón es que cada matriz conoce su tipo de elemento durante el tiempo de ejecución, mientras que la colección genérica no lo sabe debido al borrado de tipo.

Por ejemplo:

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

Si esto estuviera permitido con colecciones genéricas:

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

Pero esto causaría problemas más adelante cuando alguien intentara acceder a la lista:

String first = strings.get(0); // would cause ClassCastException, trying to assign 12 to String
Katona avatar Sep 06 '2013 21:09 Katona

Puede ser esta ayuda: -

Los genéricos no son covariantes

Las matrices en el lenguaje Java son covariantes, lo que significa que si Integer extiende Number (lo cual es así), entonces un Integer no solo es también un Número, sino que un Integer[] también es un Number[], y usted es libre de pasar o asignar un Integer[]donde Number[]se requiere a. (Más formalmente, si Número es un supertipo de Integer, entonces Number[]es un supertipo de Integer[]). Se podría pensar que lo mismo se aplica también a los tipos genéricos: es List<Number>un supertipo de List<Integer>y que se puede pasar a List<Integer>donde List<Number>se espera a. Desafortunadamente, no funciona de esa manera.

Resulta que hay una buena razón por la que no funciona de esa manera: rompería el tipo de seguridad que se supone que brindan los genéricos. Imagina que pudieras asignar un List<Integer>a un List<Number>. Luego, el siguiente código le permitiría poner algo que no fuera un número entero en List<Integer>:

List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));

Debido a que ln es un List<Number>, agregarle un Float parece perfectamente legal. Pero si ln tuviera el alias de li, entonces rompería la promesa de seguridad de tipos implícita en la definición de li: que es una lista de números enteros, razón por la cual los tipos genéricos no pueden ser covariantes.

Rahul Tripathi avatar Sep 06 '2013 21:09 Rahul Tripathi

Una característica importante de los tipos paramétricos es la capacidad de escribir algoritmos polimórficos, es decir, algoritmos que operan en una estructura de datos independientemente del valor de su parámetro, como por ejemplo Arrays.sort().

Con los genéricos, eso se hace con tipos comodín:

<E extends Comparable<E>> void sort(E[]);

Para que sean realmente útiles, los tipos comodín requieren la captura de comodines, y eso requiere la noción de un parámetro de tipo. Nada de eso estaba disponible en el momento en que se agregaron las matrices a Java, y la creación de matrices de tipo covariante de referencia permitió una forma mucho más sencilla de permitir algoritmos polimórficos:

void sort(Comparable[]);

Sin embargo, esa simplicidad abrió una laguna en el sistema de tipos estáticos:

String[] strings = {"hello"};
Object[] objects = strings;
objects[0] = 1; // throws ArrayStoreException

requiriendo una verificación en tiempo de ejecución de cada acceso de escritura a una matriz de tipo de referencia.

En pocas palabras, el enfoque más nuevo incorporado por los genéricos hace que el sistema de tipos sea más complejo, pero también más seguro de tipo estático, mientras que el enfoque anterior era más simple y menos seguro de tipo estático. Los diseñadores del lenguaje optaron por el enfoque más simple, teniendo cosas más importantes que hacer que cerrar una pequeña laguna en el sistema de tipos que rara vez causa problemas. Más tarde, cuando se estableció Java y se atendieron las necesidades apremiantes, tenían los recursos para hacerlo bien con los genéricos (pero cambiarlo por matrices habría roto los programas Java existentes).

meriton avatar Sep 13 '2013 17:09 meriton