子类型对于参数化类型是不变的。即使强壮的类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
}
请注意,方法主体的翻译代码相同。编译器通过擦除来替换每个参数化类型。此属性至关重要,意味着它不会破坏向后兼容性。
总而言之,参数化类型无法实现运行时安全性,因为编译器会通过擦除来替换每种参数化类型。这使得参数化类型仅是语法糖。