Java 类型转换和泛型原理(JVM 层面)
一、类型转换
概念解释:
编译类型:在编译时确定,保存在虚拟机栈的栈帧中的局部变量表中;
运行类型:在运行时确定,由保存在局部变量表中变量指向的堆中对象实例的类型决定(存储在对象头中);
合法性:运行类型必须是变量类型本身或其子类,否则会抛出 ClassCastException 等异常
在进行强制类型转换时,做的操作非常简单,检查被转换的变量指向的堆中实例类型和转换后的类型是否合法。
// 向上转向,安全的(子类包含父类的所有信息)
// animal 的编译类型是 Animal,运行类型是 Dog
Animal animal = new Dog();
// 向下转型,将animal引用转换为Dog类型的引用
Dog dog = (Dog) animal;
二、泛型
Java 泛型的底层原理是通过 泛型擦除 实现的。
- 编译时进行类型检查;
- 编译时将泛型类型擦除,替换为对应的边界类型(未指定时替换为 Object)
- 例如:List<T> -> List<Object>、List<T extends Animal> -> List<Animal>
-
也就是说 JVM 堆中的对象实例中的类型是替换后的类型;
-
- 为什么需要擦除:泛型是在 JDK 1.5 中引入的,擦除是为了兼容性;
- 例如:List<T> -> List<Object>、List<T extends Animal> -> List<Animal>
- 编译时插入强制类型转换代码
Box<String> box = new Box<>();
box.set("Hello");
String str = box.get();// 编译时生成强制类型转换代码
String str = (String) box.get();
Java 中的泛型并不是真正的泛型,因为在运行是,泛型被擦除了。
而 C++ 中通过模板的方式,在创建泛型类时,会创建和传入的类型相同的类实例。
// 模板
template <typename T>
T add(T a, T b) {return a + b;
}int result1 = add(1, 2); // 实例化为 add<int>
double result2 = add(1.5, 2.5); // 实例化为 add<double>// 编译器生成两份独立的代码
int add<int>(int a, int b) { return a + b; }
double add<double>(double a, double b) { return a + b; }