泛型原始类型使用疑问:为何出现ClassCastException运行时异常?
这个问题其实是Java泛型的类型擦除和编译时隐式转换共同作用的结果,咱一步步拆解清楚:
先理清背后的运行逻辑
首先得明确:Java的泛型是编译时特性,运行时JVM根本不知道泛型参数的存在——编译后所有泛型都会被「类型擦除」到它的上界(如果没指定上界,默认就是Object)。所以你的泛型类/方法里的T,在运行时实际就是Object类型。
那为什么会出现你看到的现象?大概率你的代码是类似这样的场景:
// 泛型类定义 class GenericHolder<T> { private T value; public void setValue(T value) { this.value = value; } public T getValue() { return value; } } public class Main { public static void main(String[] args) { // 先创建原始类型的实例(不带泛型参数) GenericHolder rawHolder = new GenericHolder(); // 存入一个Double对象 rawHolder.setValue(1.0); // 把原始类型实例赋值给带<Float>泛型的变量(编译会有警告,但允许运行) GenericHolder<Float> floatHolder = rawHolder; // 输出实际对象的类型 System.out.println(floatHolder.getValue().getClass()); // 尝试赋值给Float变量,抛出异常 Float f = floatHolder.getValue(); } }
为什么会输出java.lang.Double?
当你调用floatHolder.getValue().getClass()时,getClass()是运行时方法,它只认对象的真实类型——你之前给rawHolder存的是Double对象,所以不管泛型参数写的是<Float>,运行时拿到的就是Double的Class对象,自然输出java.lang.Double。
这里要注意:泛型参数<Float>只是编译时给编译器看的,运行时JVM完全不知道floatHolder应该存Float,它只知道getValue()返回的是Object类型的对象,而这个对象实际是Double。
为什么会抛出ClassCastException?
关键就在赋值那一步!编译器在编译阶段,会根据你声明的泛型参数<Float>,自动给floatHolder.getValue()插入一个隐式的强制类型转换。也就是说,编译后的代码其实是这样的:
Float f = (Float) floatHolder.getValue();
运行时,floatHolder.getValue()返回的实际是Double对象,而Double和Float是平级的子类,不能互相强制转换,于是就抛出了Exception in thread "main" java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Float。
解答你的核心疑问:T并没有被替换成Double
你觉得“T被替换为Double”其实是误解啦!类型擦除后T在运行时就是Object,你看到的Double是实际存储的对象类型,不是泛型参数的运行时类型。泛型在运行时完全不存在,JVM根本不知道T应该是Float还是Double,它只处理Object类型的对象。输出Double仅仅是因为你存的是Double对象,和泛型参数没有关系。
内容的提问来源于stack exchange,提问作者Rishab Shinghal

