LibGDX 数组的内部数组类型不安全

LibGDX Array's internal array is not type safe

我正在以一种通用的方式从 LibGDX 扩展 Array extends Array<MyClass> 但是当我直接访问它的 items 数组时,我得到一个错误 Object 无法转换为 class MyClass.

我不明白为什么会出现此错误。

public class MyArray extends Array<MyClass> {

    public void method() {
        for (MyClass myItem : items)//throws java.lang.Object; cannot be cast to class error on this line
            System.out.println(myItem);
    }
}

我还注意到,如果我尝试用 items[2] 做类似的事情会抛出同样的错误,而 get(2) 没有问题...

编辑:这是我从 LibGDX Array

使用的 Array

说明

其根本原因是,在 Java 中,泛型在 运行 时间 被 删除。所以 LibGDX 的 T[] items 在编译后只是一个普通的 Object[].

因此,虽然您的代码确实可以编译,因为您使用了正确的类型,但是当您 运行 它时,Java 检测到一个可能的问题,因为您试图将 Object 视为MyClass。因为,在 运行 时,数组只是一个 Object[],它的所有内容都是 Object。所以泛型在 运行 时间内无法保持活动状态。


反思

真正拥有 true T[] 的唯一方法是通过使用实际真实类型的反射动态创建它,作为标记给出。 LibGDX 为此提供了一个构造函数:

public Array (boolean ordered, int capacity, Class arrayType) {
    this.ordered = ordered;
    items = (T[]) ArrayReflection.newInstance(arrayType, capacity);
}

这样,数组在 运行 时也将是 T[] 类型。它实际上也在 source code:

中评论了这一点

Provides direct access to the underlying array. If the Array's generic type is not Object, this field may only be accessed if the Array#Array(boolean, int, Class) constructor was used.


get

get 调用有效,因为在这种情况下 Java 足够聪明,可以确定擦除实际上是安全的。因此,虽然 MyClass foo = get(index); 确实会衰减到 MyClass foo = (MyClass) get(index);,但 Java 知道这是一个 safe cast.

在您直接使用数组的示例中,Java 无法解决并失败。对于确切的细节,您可能需要深入研究 JLS。


items.length

在您的 child class 中以任何方式使用 items 都会立即触发问题,所以即使是这个看似无辜的片段:

int size = items.length;

这是一个非常技术性的边缘案例,也是 Java 通用系统的局限性。您的 child class 在使用 items 时期望 MyClass[] 但它在 运行 时得到 Object[],这不是它想要的。所以它触发了错误。


另一个例子

这是另一个在不使用 LibGDX 的情况下重现问题的最小示例。您可以轻松重现所见情况。

public class Test {
    public static void main(String[] args) {
        Child child = new Child();
        child.printAll();
        System.out.println(child.size());
    }

    private static class Parent<T> {
        protected T[] items;
        
        public Parent() {
            items = (T[]) new Object[10];
        }
    }

    private static class Child extends Parent<String> {
        public Child() {
            items[0] = "hello"; // Fails at runtime
        }

        public void printAll() {
            for (String s : items) { // Fails at runtime
                System.out.println(s);
            }
        }

        public int size() {
            return items.length; // Fails at runtime
        }
    }
}