Podtypowanie jest niezmienne dla sparametryzowanych typów. Mimo że klasa Dog
jest podtypem Animal
, sparametryzowany typ List<Dog>
nie jest podtypem List<Animal>
. W przeciwieństwie do tego, tablice wykorzystują kowariantne podtypy, więc typ tablicy Dog[]
jest podtypem Animal[]
.
Niezmienne podtypowanie zapewnia, że ograniczenia typu wymuszone przez Javę nie zostaną naruszone. Rozważ następujący kod podany przez @Jona Skeeta:
List<Dog> dogs = new ArrayList<Dog>(1);
List<Animal> animals = dogs;
animals.add(new Cat()); // compile-time error
Dog dog = dogs.get(0);
Jak stwierdził @Jon Skeet, ten kod jest nielegalny, ponieważ w przeciwnym razie naruszyłby ograniczenia typu, zwracając kota, gdy pies tego oczekiwał.
Pouczające jest porównanie powyższego z analogicznym kodem dla tablic.
Dog[] dogs = new Dog[1];
Object[] animals = dogs;
animals[0] = new Cat(); // run-time error
Dog dog = dogs[0];
Kod jest legalny. Zgłasza jednak wyjątek magazynu tablic . Tablica przenosi swój typ w czasie wykonywania, w ten sposób JVM może wymusić bezpieczeństwo typu podtypu kowariantnego.
Aby to zrozumieć, spójrzmy na kod bajtowy wygenerowany przez javap
poniższą klasę:
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");
}
}
Za pomocą polecenia javap -c Demonstration
wyświetla się następujący kod bajtowy 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
}
Zauważ, że przetłumaczony kod treści metod jest identyczny. Kompilator zastąpił każdy sparametryzowany typ swoim skasowaniem . Ta właściwość ma kluczowe znaczenie, co oznacza, że nie złamała kompatybilności wstecznej.
Podsumowując, bezpieczeństwo w czasie wykonywania nie jest możliwe dla sparametryzowanych typów, ponieważ kompilator zastępuje każdy sparametryzowany typ przez jego usunięcie. To sprawia, że sparametryzowane typy są niczym więcej niż cukrem syntaktycznym.