为何在增强for循环中调用泛型类数组Getter会抛出ClassCastException?
这个问题的核心在于Java泛型的类型擦除机制和数组的运行时类型强校验特性之间的冲突,咱们一步步拆解清楚:
构造器里的"假强转"
你在构造器里写的(T[]) new ScrollingObject[size]是一个未检查的强制转换——编译时编译器因为@SuppressWarnings("unchecked")不会报错,但运行时,这个数组的实际类型还是ScrollingObject[]。泛型参数T的类型信息在编译后已经被擦除了,JVM根本不知道T到底是TubePair还是Ground。getAll()的类型欺骗
getAll()方法声明返回T[],编译时编译器会帮你做类型推断,但运行时返回的还是那个ScrollingObject[]实例。当你在外部用for(TubePair tubePair : tubePairHandler.getAll())的时候,JVM会尝试把ScrollingObject[]转换成TubePair[]——而数组是协变的,不同类型的数组在运行时是完全独立的对象,这个转换必然失败,所以抛出ClassCastException。
List<T>(最推荐) Java的集合框架对泛型的支持比数组友好得多,直接把数组换成List<T>就能彻底解决这个问题,代码修改如下:
public abstract class ScrollingObjectHandler<T extends ScrollingObject> { private List<T> objects; // 把数组替换为List // 其他字段.... protected ScrollingObjectHandler(int size) { // 其他字段初始化.... objects = new ArrayList<>(size); for(int i = 0;i < size;i++) { objects.add(create()); // 用add方法添加元素 } } protected abstract T create(); // 返回List<T>,而非数组 public List<T> getAll() { return objects; // 如果不想暴露内部List的可修改性,可以返回Collections.unmodifiableList(objects); } // 其余代码.... }
外部调用代码几乎不用改动,增强for循环直接遍历List即可:
// 碰撞检测代码 for(TubePair tubePair : tubePairHandler.getAll()) { // 碰撞检测逻辑.... } // Ground绘制代码 for(Ground ground : world.getGrounds()) { // 绘制逻辑.... } // 修改getGrounds方法 List<Ground> getGrounds() { return groundHandler.getAll(); }
如果因为性能原因必须使用数组,那么需要在创建数组时就指定具体的类型,这需要你在构造器中传入Class<T>的实例,用Array.newInstance来创建正确类型的数组:
public abstract class ScrollingObjectHandler<T extends ScrollingObject> { private T[] objects; // 其他字段.... @SuppressWarnings("unchecked") protected ScrollingObjectHandler(int size, Class<T> clazz) { // 其他字段初始化.... // 直接创建T类型的数组 objects = (T[]) Array.newInstance(clazz, size); for(int i = 0;i < size;i++) { objects[i] = create(); } } protected abstract T create(); public T[] getAll() { return objects; } // 其余代码.... }
初始化处理器时需要传入Class对象:
ScrollingObjectHandler<TubePair> tubePairHandler = new ScrollingObjectHandler<TubePair>(size, TubePair.class) { @Override public TubePair create() { // 返回新的TubePair实例 } }; ScrollingObjectHandler<Ground> groundHandler = new ScrollingObjectHandler<Ground>(size, Ground.class) { @Override public Ground create() { // 返回新的Ground实例 } };
这样创建出来的数组实际类型就是TubePair[]或Ground[],运行时类型转换就不会出错了。
内容的提问来源于stack exchange,提问作者Moataz Abdelnasser

