Il sottotipo è invariante per i tipi con parametri. Anche se dura la classe Dog
è un sottotipo di Animal
, il tipo con parametri List<Dog>
non è un sottotipo di List<Animal>
. Al contrario, il sottotipo covariante viene utilizzato dagli array, quindi il tipo di array Dog[]
è un sottotipo di Animal[]
.
Il sottotipo invariante garantisce che i vincoli di tipo applicati da Java non vengano violati. Considera il seguente codice fornito da @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);
Come affermato da @Jon Skeet, questo codice è illegale, perché altrimenti violerebbe i vincoli di tipo restituendo un gatto quando previsto da un cane.
È istruttivo confrontare quanto sopra con il codice analogo per le matrici.
Dog[] dogs = new Dog[1];
Object[] animals = dogs;
animals[0] = new Cat(); // run-time error
Dog dog = dogs[0];
Il codice è legale Tuttavia, genera un'eccezione del negozio di array . Un array porta il suo tipo in fase di esecuzione in questo modo JVM può imporre la sicurezza del tipo del sottotipo covariante.
Per capirlo ulteriormente, diamo un'occhiata al bytecode generato dalla javap
classe seguente:
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 il comando javap -c Demonstration
, questo mostra il seguente 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
}
Osservare che il codice tradotto dei corpi dei metodi è identico. Il compilatore ha sostituito ogni tipo con parametri con la sua cancellazione . Questa proprietà è fondamentale, nel senso che non ha interrotto la retrocompatibilità.
In conclusione, la sicurezza di runtime non è possibile per i tipi con parametri, poiché il compilatore sostituisce ogni tipo con parametri con la sua cancellazione. Questo rende i tipi con parametri non sono altro che zucchero sintattico.