Java泛型这一重要特性是在JDK 1.5版本中首次引入的。为了确保与早期版本的兼容性,Java泛型的实现采用了一种被形象地称为“伪泛型”的策略。也就是说,尽管Java在语法上支持泛型,但在程序的编译阶段,会进行所谓的“类型擦除”(Type Erasure),即将所有泛型表示替换为具体的类型。
这样处理后,代码就像完全没有使用泛型一样。 要理解泛型的真正目的,首先需要明白其根本目的是为了实现参数化类型(在不创建新类型的情况下,通过泛型指定的不同类型来约束形参的具体类型)。
这意味着,在泛型的使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口和泛型方法。 泛型的主要优势在于它可以适用于多种数据类型执行相同的代码,从而实现代码复用。在泛型使用过程中,类型在使用时指定,无需进行强制类型转换,因为编译器会负责检查类型,确保类型安全。
泛型上下限
为了解决泛型中可能出现的类型转换问题,Java泛型引入了类型参数的上下边界机制。这种机制通过使用“extends”关键字来定义上边界和“super”关键字来定义下边界。
其中,使用“extends A”表示该类型参数可以是A的子类或A本身。在编译时,类型参数会被擦除并替换为具体的类型A,从而确保了类型的安全性。
这种机制可以有效解决在泛型中使用强制类型转换的问题。编译器可以清楚地知道类型参数的取值范围,只有当传入的实例类型在这个范围内时才允许进行转换。这样,只需要进行一次类型转换即可,运行时会把对象当做A的实例来处理。
此外,使用上下边界机制还可以提高代码的可读性和可维护性。通过明确指定类型参数的取值范围,可以让代码更加清晰易懂,降低维护成本。
Java泛型的上下边界机制是一种非常实用的特性,可以有效解决泛型中可能出现的类型转换问题,提高代码的可读性和可维护性,并且使得代码更加灵活和通用。
什么是泛型擦除
探讨什么是泛型擦除之前,大家先来看一个小demo
public static void main(String[] args) throws Exception {
List<Integer> integers = new ArrayList<>();
Class listClass = integers.getClass();
listClass.getDeclaredMethod("add", Object.class).invoke(integers, "JAVA新视界");
System.out.println(JSONObject.toJSONString(integers));
}
程序输出
["JAVA新视界"]
为什么泛型为<Integer>的列表中,可以保存"JAVA新视界"的字符串呢?
如何理解Java中的泛型是伪泛型?泛型中类型擦除
Java中的泛型并不完全是真正的泛型,因为Java在编译时会对泛型进行类型擦除(Type Erasure)。这意味着在编译后的字节码中,泛型类型信息会被擦除,取而代之的是具体的原生类型(Raw Type)。
虽然Java泛型在语法上看起来像是泛型,但在编译阶段,泛型参数会被替换为具体的类型。例如,一个泛型列表List<Integer>在编译后的字节码中实际上变成了一个List<Object>,而不是真正的泛型类型。
这种实现方式是为了保持与早期版本的兼容性,同时提供一些泛型的优点,如类型安全和代码复用。通过类型擦除,Java可以在编译时检查类型,确保类型安全,同时避免运行时出现类型转换异常。
虽然Java的泛型实现不是真正的泛型,但它仍然提供了很多好处。它可以帮助我们编写更加类型安全和可维护的代码,避免在运行时出现类型转换异常,并且可以提高代码的可重用性。因此,尽管Java中的泛型是“伪泛型”,但在实际编程中仍然非常有用。
泛型的类型擦除原则是:
删除泛型参数的声明,即删除<>及其包围的部分
根据类型参数的上下界推断并替换所有的类型参数为原生态类型。
对于无限制通配符或没有上下界限定的类型参数,将其替换为Object类型。
对于存在上下界限定的类型参数,根据子类替换原则获取父类的限定类型作为替换类型。
为了保证类型安全,必要时插入强制类型转换代码。
自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”
那么如何进行擦除的呢?
- 擦除类定义中的类型参数 - 无限制类型擦除
当类定义中的类型参数没有任何限制时,在类型擦除中将会直接被替换为Object,如<T>和<?>的类型参数都被替换为超类Object。
- 擦除类定义中的类型参数 - 有限制类型擦除
当类定义中的类型参数存在限制(上下界)时,在类型擦除中需要替换为类型参数的上界或者下界,比如<T extends Number>和<? extends Number>的类型参数被替换为父类Number,<? super Number>被替换为超类Object。
- 擦除方法定义中的类型参数
擦除方法定义中的类型参数和擦除类定义中的类型参数是一样的,以擦除方法定义中的有限制类型参数为例:
现在再返回来看刚开始引出的问题,为什么泛型为<Integer>的列表中,可以保存"JAVA新视界"的字符串,因为在运行过程中,会将List<Integer>泛型擦除,变为List<Object>,而注入字符串也是程序在运行阶段执行的,List<Object>支持注入字符串,是不是就解释得通了呢?