Подтипирование является инвариантным для параметризованных типов. Даже если класс 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
}
Обратите внимание, что переведенный код тела метода идентичен. Компилятор заменил каждый параметризованный тип его стиранием . Это свойство имеет решающее значение, так как оно не нарушает обратную совместимость.
В заключение, безопасность во время выполнения невозможна для параметризованных типов, поскольку компилятор заменяет каждый параметризованный тип его стиранием. Это делает параметризованные типы не более чем синтаксическим сахаром.