子类型对于参数化类型是不变的。即使严格来说,类Dog是Animal的子类型,但参数化类型List<Dog>不是List<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演示,这将显示以下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
}
观察方法体的翻译代码是否相同。编译器通过删除来替换每个参数化类型。此属性至关重要,这意味着它不会破坏向后兼容性。
总之,参数化类型的运行时安全性是不可能的,因为编译器通过删除来替换每个参数化类型。这使得参数化类型只不过是语法糖。