¿Por qué los genéricos en Java funcionan con clases pero no con tipos primitivos?
Por ejemplo, esto funciona bien:
List<Integer> foo = new ArrayList<Integer>();
pero esto no está permitido:
List<int> bar = new ArrayList<int>();
¿Por qué los genéricos en Java funcionan con clases pero no con tipos primitivos?
Por ejemplo, esto funciona bien:
List<Integer> foo = new ArrayList<Integer>();
pero esto no está permitido:
List<int> bar = new ArrayList<int>();
Respuestas:
Los genéricos en Java son una construcción completamente en tiempo de compilación: el compilador convierte todos los usos genéricos en conversiones al tipo correcto. Esto es para mantener la compatibilidad con versiones anteriores de tiempos de ejecución JVM.
Esta:
List<ClassA> list = new ArrayList<ClassA>();
list.add(new ClassA());
ClassA a = list.get(0);
se convierte en (aproximadamente):
List list = new ArrayList();
list.add(new ClassA());
ClassA a = (ClassA)list.get(0);
Por lo tanto, cualquier cosa que se use como genéricos debe ser convertible a Object (en este ejemplo get(0)
devuelve un Object
), y los tipos primitivos no lo son. Por lo tanto, no se pueden usar en genéricos.
En Java, los genéricos funcionan de la manera en que lo hacen ... al menos en parte ... porque se agregaron al lenguaje varios años después de que el lenguaje fue diseñado 1 . Los diseñadores de idiomas se vieron limitados en sus opciones de genéricos al tener que idear un diseño que fuera compatible con el lenguaje existente y la biblioteca de clases Java. .
Otros lenguajes de programación (por ejemplo, C ++, C #, Ada) permiten que se usen tipos primitivos como tipos de parámetros para genéricos. Pero la otra cara de hacer esto es que las implementaciones de genéricos (o tipos de plantillas) de tales lenguajes generalmente implican la generación de una copia distinta del tipo genérico para cada parametrización de tipo.
1 - La razón por la cual los genéricos no se incluyeron en Java 1.0 se debió a la presión del tiempo. Sintieron que tenían que obtener el lenguaje Java lanzado rápidamente para llenar la nueva oportunidad de mercado presentada por los navegadores web. James Gosling ha declarado que le hubiera gustado incluir genéricos si hubieran tenido tiempo. Lo que habría parecido el lenguaje Java si esto hubiera sucedido es una incógnita.
En java, los genéricos se implementan mediante el uso de "Borrado de tipo" para la compatibilidad con versiones anteriores. Todos los tipos genéricos se convierten en objetos en tiempo de ejecución. por ejemplo,
public class Container<T> {
private T data;
public T getData() {
return data;
}
}
será visto en tiempo de ejecución como,
public class Container {
private Object data;
public Object getData() {
return data;
}
}
El compilador es responsable de proporcionar un molde adecuado para garantizar la seguridad del tipo.
Container<Integer> val = new Container<Integer>();
Integer data = val.getData()
se convertirá
Container val = new Container();
Integer data = (Integer) val.getData()
Ahora la pregunta es ¿por qué "Objeto" se elige como tipo en tiempo de ejecución?
La respuesta es Object es la superclase de todos los objetos y puede representar cualquier objeto definido por el usuario.
Como todas las primitivas no heredan del " Objeto ", no podemos usarlo como un tipo genérico.
FYI: Proyecto Valhalla está tratando de abordar el problema anterior.
Las colecciones se definen para requerir un tipo derivado de java.lang.Object
. Los tipos básicos simplemente no hacen eso.
Según la documentación de Java , las variables de tipo genérico solo se pueden instanciar con tipos de referencia, no con tipos primitivos.
Se supone que esto vendrá en Java 10 bajo el Proyecto Valhalla .
En el documento de Brian Goetz sobre el estado de la especialización
Hay una excelente explicación sobre la razón por la cual los genéricos no eran compatibles con los primitivos. Y cómo se implementará en futuras versiones de Java.
La implementación borrada actual de Java que produce una clase para todas las instancias de referencia y no es compatible con las instancias primitivas. (Esta es una traducción homogénea, y la restricción de que los genéricos de Java solo pueden variar sobre tipos de referencia proviene de las limitaciones de la traducción homogénea con respecto al conjunto de códigos de bytes de la JVM, que utiliza diferentes códigos de bytes para operaciones en tipos de referencia frente a tipos primitivos). Sin embargo, los genéricos borrados en Java proporcionan tanto la parametricidad del comportamiento (métodos genéricos) como la parametricidad de los datos (instancias sin formato y comodines de tipos genéricos).
...
Se eligió una estrategia de traducción homogénea, donde las variables de tipo genérico se borran hasta sus límites a medida que se incorporan al código de bytes. Esto significa que si una clase es genérica o no, aún se compila en una sola clase, con el mismo nombre y cuyas firmas de miembros son las mismas. La seguridad de tipo se verifica en tiempo de compilación, y el sistema de tipo genérico no limita el tiempo de ejecución. A su vez, esto impuso la restricción de que los genéricos solo podían funcionar sobre los tipos de referencia, ya que Object es el tipo más general disponible, y no se extiende a los tipos primitivos.
Al crear un objeto, no puede sustituir un tipo primitivo por el parámetro de tipo. En cuanto al por qué de esta restricción, es un problema de implementación del compilador. Los tipos primitivos tienen sus propias instrucciones de código de bytes para cargar y almacenar en la pila de máquinas virtuales. Por lo tanto, no es imposible compilar genéricos primitivos en estas rutas de bytecode separadas, pero complicaría el compilador.