为何无法将Object数组转为泛型类的泛型子类数组?
你遇到的这个差异,核心原因要结合Java的泛型类型擦除机制和数组运行时类型检查特性来解释,咱们逐个例子拆解:
一、第一个例子:仅触发编译警告的原因
先看这段代码:
class Foo<T> { T[] items = (T[]) new Object[10]; public static void main(String[] args) { Foo<Integer> foo = new Foo<Integer>(); } }
由于Java泛型是编译时擦除的,运行时T会被替换成它的默认上限(这里没有指定上限,所以是Object)。当你把Object[]强转为T[]时,运行时JVM其实看不到T的具体类型,这个强转本质上是Object[]转Object[]——完全合法,所以不会触发运行时异常。
那为什么会有警告?因为编译器知道你在做一个“不安全”的操作:如果后续你把这个数组当成Integer[]使用(比如往里面塞String),会在其他地方抛出类型转换异常,但当前代码只是创建实例,没触发危险操作,所以只有编译警告。
二、第二个例子:非静态内部类导致运行时异常
再看这段带非静态内部类的代码:
class Foo<T> { class FooItem { T item; } FooItem[] items = (FooItem[]) new Object[10]; public static void main(String[] args) { Foo<Integer> foo = new Foo<Integer>(); } }
这里的关键是非静态内部类FooItem是一个具体的类(字节码里叫Foo$FooItem),它不是泛型擦除后的Object。当你把Object[]强转为FooItem[]时,运行时JVM会做真实的类型检查:Object[]的实际类型是[Ljava.lang.Object;,而你要转的是[LFoo$FooItem;——这两个类型完全没有继承关系,所以直接抛出ClassCastException。
和第一个例子的区别在于:第一个例子的T[]擦除后是Object[],强转是“自洽”的;而这里的FooItem[]是实实在在的具体类数组,和Object[]类型不兼容。
三、第三个例子:静态泛型类导致运行时异常
最后看这段带静态泛型类的代码:
class Foo<T> { static class FooItem<E> { E item; } FooItem<T>[] items = (FooItem<T>[]) new Object[10]; public static void main(String[] args) { Foo<Integer> foo = new Foo<Integer>(); } }
静态泛型类FooItem<E>的泛型参数E会在运行时擦除为Object,但FooItem<T>[]擦除后的实际类型是FooItem[](不是Object[])。你创建的是Object[],强转成FooItem[]时,JVM会检查数组的组件类型:Object[]的组件是Object,而FooItem[]的组件是FooItem,类型不匹配,所以直接抛出异常。
简单总结一下:
- 当强转的目标数组类型,在运行时擦除后和实际创建的
Object[]类型一致时,只会出编译警告,不会抛运行时异常; - 当目标数组类型是具体类的数组(不管是内部类还是泛型静态类的数组),和
Object[]类型不兼容时,运行时的类型检查会直接失败,抛出ClassCastException。
内容的提问来源于stack exchange,提问作者tu4n

