A subtipagem é invariável para tipos parametrizados. Por mais difícil que a classe Dog
seja um subtipo de Animal
, o tipo parametrizado List<Dog>
não é um subtipo de List<Animal>
. Por outro lado, a subtipo covariante é usada por matrizes, portanto, o tipo de matriz Dog[]
é um subtipo de Animal[]
.
A subtipagem invariável garante que as restrições de tipo impostas pelo Java não sejam violadas. Considere o seguinte código fornecido 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);
Conforme afirmado por Jon Skeet, esse código é ilegal, pois, caso contrário, violaria as restrições de tipo ao retornar um gato quando um cachorro o esperava.
É instrutivo comparar o código acima com o código análogo para matrizes.
Dog[] dogs = new Dog[1];
Object[] animals = dogs;
animals[0] = new Cat(); // run-time error
Dog dog = dogs[0];
O código é legal. No entanto, lança uma exceção de armazenamento de matriz . Uma matriz carrega seu tipo em tempo de execução, dessa maneira a JVM pode impor a segurança de tipo de subtipo coviante.
Para entender isso ainda mais, vejamos o bytecode gerado pela javap
classe abaixo:
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 o comando javap -c Demonstration
, isso mostra o seguinte bytecode 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 o código traduzido dos corpos dos métodos é idêntico. O compilador substituiu cada tipo parametrizado por sua exclusão . Essa propriedade é crucial, o que significa que não quebrou a compatibilidade com versões anteriores.
Em conclusão, a segurança em tempo de execução não é possível para tipos parametrizados, pois o compilador substitui cada tipo parametrizado por sua eliminação. Isso faz com que tipos parametrizados nada mais sejam do que açúcar sintático.