Subtipado en genéricos en Java
Vamos a analizar un caso con genéricos. ¿Es legal el siguiente fragmento de código?
1List<String> listaString = new ArrayList<String>();2List<Object> listaObject = listaString;
La línea 1 ciertamente es legal. La parte más complicada de la pregunta es la línea 2. Esto se reduce a la pregunta: ¿es una List
de String
una List
de Object
? La mayoría responde instintivamente: “¡Claro!”
Bien, ahora veamos las siguientes líneas:
1listaObject.add(new Object()); // 32String s = listaString.get(0); // 4: Intenta asignar un Object a un String!
Aquí hemos aliado listaString
y listaObject
. Accediendo a listaString
, una lista de String
, a través del alias listaObject
, podemos insertar objetos arbitrarios en ella. Como resultado, listaString
ya no contiene solo Strings
, y cuando intentamos obtener algo de ella, obtenemos algo inesperado.
Por supuesto, el compilador de Java evitará que esto suceda. La línea 2 causará un error de compilación. El error nos dice “Type mismatch: cannot convert from List<String>
to List<Object>
”.
En general, si Foo
es un subtipo (subclase o subinterfaz) de Bar
, y G es alguna declaración de tipo genérico, no es cierto que G<Foo>
sea un subtipo de G<Bar>
. Probablemente esta es lo más difícil que debemos aprender sobre los genéricos, porque va en contra de nuestras profundas intuiciones.
No debemos asumir que las colecciones no cambian. Nuestra intuición puede llevarnos a pensar en estas cosas como inmutables.
Por ejemplo, si el departamento de vehículos a motor brinda una lista de conductores al censo, esto parece razonable. Pensamos que una List<Conductor>
es una List<Persona>
, asumiendo que Conductor
es un subtipo de Persona
. De hecho, lo que se está pasando es una copia del registro de conductores. De lo contrario, el censo podría agregar nuevas personas que no son conductores a la lista, corrompiendo los registros.
Ejemplo
Supongamos que tenemos una jerarquía de clases para representar diferentes tipos de Empleados:
1public class Empleado { /*...*/ }2
3public class Ingeniero extends Empleado { /*...*/ }4
5public class Gerente extends Empleado { /*...*/ }
Si intentamos hacer lo siguiente:
1List<Empleado> empleados = new ArrayList<>();2empleados.add(new Ingeniero());3empleados.add(new Gerente());4
5List<Ingeniero> ingenieros = empleados; // Error de compilación
Obtendremos un error de compilación en la última línea. Esto se debe a que List<Ingeniero>
no es un subtipo de List<Empleado>
, a pesar de que Ingeniero
es un subtipo (subclase) de Empleado
. Esto podría causar que, por ejemplo como en la línea 3, se agregue un Gerente
a la lista de Ingeniero
, lo cual no es correcto.
Para lidiar con este tipo de situaciones, es útil considerar tipos genéricos más flexibles, utilizando comodines (wildcards).