问题历史:
Java 泛型在 Java 5 中引入,以确保安全处理集合和方法的类型,但实现时支持与之前编写的字节码的向后兼容性。为此,使用了类型擦除机制。
问题:
Java 编译器要求严格的类型检查,但在运行时 JVM 并不知道类型参数,并且许多旧类和库,包括 Java 自身的集合库,使用通用的 "原始" 类型(raw types)。没有向后兼容性就无法支持现有开发。
解决方案:
类型擦除是将参数化类型(泛型)转换为它们的 "原始" 版本的过程,以便 JVM 可以在不更改的情况下处理现有的字节码。所有类型参数的信息在编译阶段被删除,取而代之的是使用 Object 类型的对象(如果通过 extends 指定了限制,则使用限制)。
代码示例:
List<String> stringList = new ArrayList<>(); stringList.add("hello"); String s = stringList.get(0); // get 返回 Object,但编译器插入了类型转换
关键特点:
List<String>,List<Integer> 等。是否可以仅通过泛型参数重载方法?
不可以。由于类型擦除,编译器将具有相同名称和不同泛型参数的方法视为相同,因为参数将被擦除。例如,
// 编译错误! void process(List<String> list) { } void process(List<Integer> list) { }
是否可以创建泛型类型的数组?
不直接可以。类型擦除不允许 JVM 存储特定泛型类型的数组,例如,
List<String>[] array = new List<String>[10]; // 编译错误
可以通过原始类型的数组绕过,但这不安全:
List<String>[] array = (List<String>[]) new List[10];
是否可以通过 instanceof 在运行时检查泛型类型?
不可以,因为类型参数的信息被擦除。检查:
if (obj instanceof List<String>) { ... } // 编译错误
更好的方式是只检查原始类型:
if (obj instanceof List) { ... }
程序员创建参数化类型数组以存储不同参数列表。 最终在程序运行一段时间后,从数组中提取对象时发生 ClassCastException——在运行时没有提供类型检查。
优点:
缺点:
使用集合(例如 List<List<String>>)而不是数组,所有类型检查都委托给编译器。
优点:
缺点: