Die Subtypisierung ist für parametrisierte Typen unveränderlich . Selbst wenn die Klasse Dog
ein Subtyp von ist Animal
, ist der parametrisierte Typ List<Dog>
kein Subtyp von List<Animal>
. Im Gegensatz dazu wird die kovariante Subtypisierung von Arrays verwendet, sodass der Array-Typ Dog[]
ein Subtyp von ist Animal[]
.
Durch die invariante Untertypisierung wird sichergestellt, dass die von Java erzwungenen Typeinschränkungen nicht verletzt werden. Betrachten Sie den folgenden Code von @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);
Wie von @Jon Skeet angegeben, ist dieser Code illegal, da er sonst die Typbeschränkungen verletzen würde, indem eine Katze zurückgegeben wird, wenn ein Hund dies erwartet.
Es ist lehrreich, das Obige mit analogem Code für Arrays zu vergleichen.
Dog[] dogs = new Dog[1];
Object[] animals = dogs;
animals[0] = new Cat(); // run-time error
Dog dog = dogs[0];
Der Code ist legal. Löst jedoch eine Array-Speicherausnahme aus . Ein Array trägt seinen Typ zur Laufzeit auf diese Weise, sodass JVM die Typensicherheit der kovarianten Subtypisierung erzwingen kann.
Um dies weiter zu verstehen, schauen wir uns den Bytecode an, der javap
von der folgenden Klasse generiert wurde :
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");
}
}
Mit dem Befehl javap -c Demonstration
wird der folgende Java-Bytecode angezeigt:
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
}
Beachten Sie, dass der übersetzte Code der Methodenkörper identisch ist. Der Compiler ersetzte jeden parametrisierten Typ durch seine Löschung . Diese Eigenschaft ist von entscheidender Bedeutung, da sie die Abwärtskompatibilität nicht beeinträchtigt.
Zusammenfassend ist die Laufzeitsicherheit für parametrisierte Typen nicht möglich, da der Compiler jeden parametrisierten Typ durch seine Löschung ersetzt. Dies macht parametrisierte Typen nichts anderes als syntaktischen Zucker.