你说的是不同的东西。
教程中的引文谈到了类型实例化。这与类型擦除无关,这是恕我直言错误命名的概念,只是意味着泛型类型在运行时不再可用。
但是在编译时它们是,并且实例化发生在编译时。
要回答您的问题,“在编译时”是一个广泛的问题。以下情况在编译时发生:
- 读取源文件
- 词法分析
- 解析
- ...
- 类型检查
- ...
- 代码生成
请注意,该列表绝不是完整的。但是,如您所见,在类型检查期间,编译器知道您的类型实例化并可以检查它们。
后来,它发出字节码,由于字节码无法表示泛型,类型被“擦除”,这意味着在这里和那里插入了一个强制转换。
因此,您认为“编译时间”在某种程度上是所有事情同时发生的瞬间的假设是不正确的。
进一步编辑:
我认为您对所有这些(即“替换”一词)的理解过于从字面上理解了。当然,编译器有一些数据结构,其中保存了程序中所有项目的类型、名称和范围。
看,原则上很简单,如果我们有:
static <X> List<X> meth(X[] arr) { .... }
后来,你这样做:
Integer arr = new Integer[100];
List<Integer> list = meth(arr);
Integer foo = list.get(1);
然后你正在实例化 meth 方法的类型:
static List<Integer> meth(Integer[] arr) { .... }
泛型的意义在于它meth
适用于任何类型。这正是编译器检查的内容。它会知道,对于所有 X,如果你传递一个 X 的数组,你会得到一个 X 的列表,因此,由于你传递了 Integer[],结果必须是List<Integer>
并且list
赋值是正确的。此外,编译器知道 ** 对于所有 X **,如果您从 a 中获取一个元素List<X>
,它将是一个 X。
因此,编译器会记录并检查 foo 是否为Integer
. 稍后,在代码生成时,它将在那里插入一个转换为 Integer,因为由于类型擦除,List.get 的返回值是 Object。
另请注意,“替换”并不意味着编译器会以某种方式更改您的代码。它只是从泛型类型签名创建(可能是临时的)一个非泛型签名(通过替换 - 如果你更喜欢这个 - 所有类型参数及其实际类型),并使用它来检查类型。
就像在数学中一样,如果我说:请将 a 替换为 42 并检查等式是否为真:
一个 + 1 = 43
那么问这个替换发生的“确切位置”是没有意义的。很可能在你的大脑中。