Підтипізація є інваріантною для параметризованих типів. Навіть жорсткий клас Dog
є підтипом Animal
, параметризований тип List<Dog>
не є підтипом List<Animal>
. Навпаки, коваріантне підтипування використовується масивами, тому тип масиву Dog[]
є підтипом Animal[]
.
Інваріантне підтипування гарантує, що обмеження типу, що застосовуються Java, не порушуються. Розглянемо наступний код, поданий @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);
Як заявив @Jon Skeet, цей код є незаконним, оскільки в іншому випадку він може порушити типові обмеження, повернувши кота, коли собака очікувала.
Доцільно порівнювати вищезазначене з аналогічним кодом для масивів.
Dog[] dogs = new Dog[1];
Object[] animals = dogs;
animals[0] = new Cat(); // run-time error
Dog dog = dogs[0];
Код законний. Однак, викидає виняток у магазині масивів . Масив несе свій тип під час виконання таким чином JVM може забезпечити безпеку типу коваріантного підтипу.
Щоб зрозуміти це далі, давайте подивимося на байт-код, згенерований javap
класом нижче:
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");
}
}
За допомогою команди javap -c Demonstration
відображається наступний байт-код 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
}
Зауважте, що перекладений код тестів методу ідентичний. Компілятор замінив кожен параметризований тип його стиранням . Ця властивість має вирішальне значення, оскільки вона не порушувала зворотної сумісності.
На закінчення, безпека під час запуску неможлива для параметризованих типів, оскільки компілятор замінює кожен параметризований тип його стиранням. Це робить параметризовані типи не що інше, як синтаксичний цукор.