De Java efectivo por Joshua Bloch,
- Las matrices difieren del tipo genérico en dos formas importantes. Las primeras matrices son covariantes. Los genéricos son invariantes.
Covariante simplemente significa que si X es un subtipo de Y, entonces X [] también será un subtipo de Y []. Las matrices son covariantes Como la cadena es un subtipo de Objeto Entonces
String[] is subtype of Object[]
Invariante simplemente significa que independientemente de que X sea subtipo de Y o no,
List<X> will not be subType of List<Y>.
Mi pregunta es ¿por qué la decisión de hacer matrices covariantes en Java? Hay otras publicaciones SO tales como ¿Por qué las matrices son invariantes, pero las listas son covariantes? , pero parecen centrarse en Scala y no puedo seguirlo.
java
arrays
generics
language-design
covariance
Ansioso por aprender
fuente
fuente
LinkedList
.Respuestas:
Vía wikipedia :
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, a propósito no se hicieron covariantes por las razones señaladas en esta respuesta por Jon Skeet :
La motivación original para hacer las matrices covariantes descritas en el artículo de Wikipedia no se aplicaba a los genéricos porque los comodines hicieron posible la expresión de covarianza (y contravarianza), por ejemplo:
fuente
Object[] num = new Number[4]; num[1]= 5; num[2] = 5.0f; num[3]=43.4; System.out.println(Arrays.toString(num)); num[0]="hello";
ArrayStoreException
s según sea necesario. Claramente, esto se consideró un compromiso digno en ese momento. Compare eso con hoy: muchos consideran la covarianza de la matriz como un error, en retrospectiva.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 a la eliminación del tipo.
Por ejemplo:
Si esto se permitió con colecciones genéricas:
Pero esto causaría problemas más tarde cuando alguien intentara acceder a la lista:
fuente
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 un Número (lo que hace), entonces no solo un Entero también es un Número, sino que un Entero [] también es un
Number[]
, y usted es libre de pasar o asignar unInteger[]
dondeNumber[]
se llama a. (Más formalmente, si Number es un supertipo de Integer, entoncesNumber[]
es un supertype deInteger[]
). También podría pensar que lo mismo es cierto para los tipos genéricos, queList<Number>
es un supertype deList<Integer>
, y que puede pasar unList<Integer>
dondeList<Number>
se espera un 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 que los genéricos de seguridad debían proporcionar. Imagina que puedes asignar un
List<Integer>
a aList<Number>
. Entonces, el siguiente código le permitiría poner algo que no fuera un número entero enList<Integer>
:Como ln es a
List<Number>
, agregar un Float parece perfectamente legal. Pero si se alias conli
, entonces rompería la promesa de seguridad de tipo implícita en la definición de li: que es una lista de enteros, por lo que los tipos genéricos no pueden ser covariantes.fuente
ArrayStoreException
tiempo de ejecución.WHY
matrices se hacen covariantes. como mencionó Sotirios, con las matrices se obtendría ArrayStoreException en tiempo de ejecución, si las matrices se hicieran invariables, ¿podríamos detectar este error en tiempo de compilación correcto?Animal
, que no tiene que aceptar ningún elemento recibido de otro lugar" de "Array que no debe contener nada más queAnimal
, y debe estar dispuesto a aceptar referencias proporcionadas externamenteAnimal
. El código que necesita el primero debe aceptar una matriz deCat
, pero el código que necesita el segundo no. Si el compilador podría distinguir los dos tipos, podría proporcionar una verificación en tiempo de compilación. Desafortunadamente, lo único que los distingue ...Las matrices son covariantes por al menos dos razones:
Es útil para colecciones que contienen información que nunca cambiará para ser covariante. Para que una colección de T sea covariante, su almacén de respaldo también debe ser covariante. Si bien uno podría diseñar una
T
colección inmutable que no utilizara aT[]
como su almacén de respaldo (por ejemplo, usando un árbol o una lista vinculada), es poco probable que dicha colección funcione tan bien como una respaldada por una matriz. Se podría argumentar que una mejor manera de proporcionar colecciones inmutables covariantes habría sido definir un tipo de "matriz inmutable covariante" en el que podrían usar un almacén de respaldo, pero simplemente permitir la covarianza de la matriz probablemente fuera más fácil.Las matrices serán frecuentemente mutadas por un código que no sabe qué tipo de cosa va a estar en ellas, pero no incluirá en la matriz nada que no haya sido leído de esa misma matriz. Un buen ejemplo de esto es el código de clasificación. Conceptualmente, podría haber sido posible que los tipos de matriz incluyeran métodos para intercambiar o permutar elementos (tales métodos podrían ser igualmente aplicables a cualquier tipo de matriz), o definir un objeto "manipulador de matriz" que contenga una referencia a una matriz y una o más cosas que se había leído de él, y podría incluir métodos para almacenar elementos leídos previamente en la matriz de la que provenían. Si las matrices no fueran covariantes, el código de usuario no podría definir dicho tipo, pero el tiempo de ejecución podría haber incluido algunos métodos especializados.
El hecho de que las matrices sean covariantes puede verse como un truco feo, pero en la mayoría de los casos facilita la creación de código de trabajo.
fuente
The fact that arrays are covariant may be viewed as an ugly hack, but in most cases it facilitates the creation of working code.
- buen puntoUna 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 de su valor de parámetro, como
Arrays.sort()
.Con los genéricos, eso se hace con los tipos comodín:
Para ser realmente útiles, los tipos de comodines requieren 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 matrices a Java, y hacer matrices de tipo covariante de referencia permitió una forma mucho más simple de permitir algoritmos polimórficos:
Sin embargo, esa simplicidad abrió un vacío en el sistema de tipo estático:
requiere una verificación de 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 más antiguo era más simple y menos seguro de tipo estático. Los diseñadores del lenguaje optaron por un enfoque más simple, que tenían cosas más importantes que hacer que cerrar un pequeño vacío 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 en genéricos (pero cambiarlo por matrices habría roto los programas Java existentes).
fuente
Los genéricos son invariantes : de JSL 4.10 :
y unas pocas líneas más, JLS también explica que las
matrices son covariantes (primer punto):
4.10.3 Subtipado entre tipos de matriz
fuente
Creo que tomaron una decisión equivocada en primer lugar que hizo que la matriz covariante. Rompe el tipo de seguridad como se describe aquí y se quedaron con eso debido a la compatibilidad con versiones anteriores y después de eso trataron de no cometer el mismo error con el genérico. Y esa es una de las razones por las que Joshua Bloch prefiere las listas a las matrices en el ítem 25 del libro "Java efectivo (segunda edición)"
fuente
Mi opinión: cuando el código espera una matriz A [] y le das B [] donde B es una subclase de A, solo hay dos cosas de las que preocuparse: qué sucede cuando lees un elemento de matriz y qué sucede si escribes eso. Por lo tanto, no es difícil escribir reglas de lenguaje para garantizar que se mantenga la seguridad de los tipos en todos los casos (la regla principal es que se
ArrayStoreException
podría arrojar un si intentas pegar una A en una B []). Sin embargo, para un genérico, cuando declaras una claseSomeClass<T>
, puede haber varias formas deT
usar el cuerpo de la clase, y supongo que es demasiado complicado calcular todas las combinaciones posibles para escribir reglas sobre cuándo las cosas están permitidas y cuando no lo están.fuente