El subtipo es invariante para los tipos parametrizados. Incluso si la clase Dog
es un subtipo de Animal
, el tipo parametrizado List<Dog>
no es un subtipo de List<Animal>
. Por el contrario, las matrices utilizan el subtipo covariante , por lo que el tipo de matriz Dog[]
es un subtipo de Animal[]
.
El subtipo invariable garantiza que no se infrinjan las restricciones de tipo impuestas por Java. Considere el siguiente código dado por @Jon Skeet:
List<Dog> dogs = new ArrayList<Dog>(1);
List<Animal> animals = dogs;
animals.add(new Cat()); // compile-time error
Dog dog = dogs.get(0);
Según lo indicado por @ Jon Skeet, este código es ilegal, porque de lo contrario violaría las restricciones de tipo al devolver un gato cuando lo esperaba un perro.
Es instructivo comparar lo anterior con un código análogo para matrices.
Dog[] dogs = new Dog[1];
Object[] animals = dogs;
animals[0] = new Cat(); // run-time error
Dog dog = dogs[0];
El código es legal. Sin embargo, arroja una excepción de tienda de matriz . Una matriz lleva su tipo en tiempo de ejecución de esta manera JVM puede hacer cumplir la seguridad de tipo de subtipo covariante.
Para entender esto aún más, veamos el bytecode generado por javap
la clase a continuación:
import java.util.ArrayList;
import java.util.List;
public class Demonstration {
public void normal() {
List normal = new ArrayList(1);
normal.add("lorem ipsum");
}
public void parameterized() {
List<String> parameterized = new ArrayList<>(1);
parameterized.add("lorem ipsum");
}
}
Usando el comando javap -c Demonstration
, esto muestra el siguiente código de bytes de Java:
Compiled from "Demonstration.java"
public class Demonstration {
public Demonstration();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void normal();
Code:
0: new #2 // class java/util/ArrayList
3: dup
4: iconst_1
5: invokespecial #3 // Method java/util/ArrayList."<init>":(I)V
8: astore_1
9: aload_1
10: ldc #4 // String lorem ipsum
12: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
17: pop
18: return
public void parameterized();
Code:
0: new #2 // class java/util/ArrayList
3: dup
4: iconst_1
5: invokespecial #3 // Method java/util/ArrayList."<init>":(I)V
8: astore_1
9: aload_1
10: ldc #4 // String lorem ipsum
12: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
17: pop
18: return
}
Observe que el código traducido de los cuerpos de los métodos es idéntico. El compilador reemplazó cada tipo parametrizado por su borrado . Esta propiedad es crucial, lo que significa que no rompió la compatibilidad con versiones anteriores.
En conclusión, la seguridad en tiempo de ejecución no es posible para los tipos parametrizados, ya que el compilador reemplaza cada tipo parametrizado por su borrado. Esto hace que los tipos parametrizados no sean más que azúcar sintáctica.