Se requiere algo de contexto para comprender completamente la razón principal detrás de esto.
Primitivos versus clases
Las variables primitivas en Java contienen valores (un número entero, un número binario de coma flotante de doble precisión, etc.). Debido a que estos valores pueden tener diferentes longitudes , las variables que los contienen también pueden tener diferentes longitudes (considere float
versus double
).
Por otro lado, las variables de clase contienen referencias a instancias. Las referencias se implementan normalmente como punteros (o algo muy similar a punteros) en muchos idiomas. Estas cosas suelen tener el mismo tamaño, independientemente de los tamaños de los casos se refieren a ( Object
, String
, Integer
, etc.).
Esta propiedad de las variables de clase hace que las referencias que contienen sean intercambiables (hasta cierto punto). Esto nos permite hacer lo que llamamos sustitución : en términos generales, usar una instancia de un tipo particular como una instancia de otro tipo relacionado (use a String
como an Object
, por ejemplo).
Las variables primitivas no son intercambiables de la misma manera, ni entre sí ni con Object
. La razón más obvia de esto (pero no la única razón) es su diferencia de tamaño. Esto hace que los tipos primitivos sean inconvenientes a este respecto, pero aún los necesitamos en el lenguaje (por razones que se reducen principalmente al rendimiento).
Genéricos y borrado de tipo
Los tipos genéricos son tipos con uno o más parámetros de tipo (el número exacto se llama aridad genérica ). Por ejemplo, la definición de tipo genérico List<T>
tiene un parámetro de tipo T
, que puede ser Object
(producir un tipo concreto List<Object>
), String
( List<String>
), Integer
( List<Integer>
) y así sucesivamente.
Los tipos genéricos son mucho más complicados que los no genéricos. Cuando se introdujeron en Java (después de su lanzamiento inicial), para evitar hacer cambios radicales en la JVM y posiblemente romper la compatibilidad con binarios más antiguos, los creadores de Java decidieron implementar tipos genéricos de la manera menos invasiva: todos los tipos concretos de List<T>
son, de hecho, compilados en (el equivalente binario de) List<Object>
(para otros tipos, el límite puede ser diferente de Object
, pero entiendes el punto). La aridad genérica y la información de los parámetros de tipo se pierden en este proceso , por lo que lo llamamos borrado de tipo .
Poniendo los dos juntos
Ahora el problema es la combinación de las realidades anteriores: si se List<T>
convierte List<Object>
en todos los casos, T
siempre debe ser un tipo al que se pueda asignar directamenteObject
. No se puede permitir nada más. Dado que, como hemos dicho antes, int
, float
y double
no son intercambiables con Object
, no puede haber una List<int>
, List<float>
o List<double>
(a menos que una aplicación mucho más complicado de los genéricos existía en la JVM).
Pero Java tipos de ofertas como Integer
, Float
y Double
que envuelven estas primitivas en las instancias de clase, por lo que efectivamente sustituibles como Object
, por lo tanto permitiendo que los tipos genéricos de forma indirecta trabajo con las primitivas así (porque se puede tener List<Integer>
, List<Float>
, List<Double>
y así sucesivamente).
El proceso de crear un Integer
desde un int
, un Float
desde un float
y así sucesivamente, se llama boxeo . Lo contrario se llama unboxing . Debido a que tener que encuadrar primitivas cada vez que desee usarlas Object
es un inconveniente, hay casos en los que el lenguaje lo hace automáticamente, lo que se llama autoencuadre .