1

I am extending Array from LibGDX in a generic fashion which extends Array<MyClass> but when I access its items array directly, I get an error that Object can not be cast to class MyClass.

I don't see exactly why I'm getting an error with this.

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);
    }
}

I noticed also that if I try to do a similar thing with items[2] throws the same error while get(2) has no problem...

EDIT: Here is the Array I used from LibGDX Array

Hasen
  • 11,710
  • 23
  • 77
  • 135
  • @Zabuzard `Array` is from LibGDX, as in the tags. I'll add it to the title. I normally put the framework name in the title but so many people edit it and delete it....then things like this happen. – Hasen Aug 09 '20 at 07:28
  • I do not really know much about that `Array` class but it seems that its internal `items` array is actually of type `Object[]` and not `T[]`, while its methods like `get` maintain typesafety of the generic. Hence direct usage of `items` will give you `Object` back which is safe to be cast to `T`. So you can just add a cast to `MyClass` and it would be safe. But you should definitely prefer not using `items` directly and instead use the methods provided by the class that still maintain type safety. – Zabuzard Aug 09 '20 at 07:31
  • @Zabuzard It does appear to be of type T, it's listed as `public T[] items;` https://libgdx.badlogicgames.com/ci/nightlies/docs/api/com/badlogic/gdx/utils/Array.html I'm extending the array because I like to access the items directly. Normally everything works fine but I have no idea why this error is being thrown. Doesn't seem to make sense. – Hasen Aug 09 '20 at 07:34
  • The doc might just be inaccurate or from a different version, have you looked up the actual source code? – Zabuzard Aug 09 '20 at 07:35
  • @Zabuzard Yes that's not from the doc, I copied it from the source code myself. I just gave the link to the class online so you could see it for yourself. – Hasen Aug 09 '20 at 07:36
  • The link you sent is the documentation and not the source code. – Zabuzard Aug 09 '20 at 07:38
  • 2
    @Zabuzard Yes I know, I said I copied it myself from the source code `public T[] items;` so that is indeed what it says. I also gave you that link so you could check the array. If you want the source code you can see it here: https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils/Array.java – Hasen Aug 09 '20 at 07:40
  • @Hasen did you see the comment above the declaration? – PeterT Aug 09 '20 at 07:44
  • Actually, I get the same issue with a minimal example as shown here: https://hastebin.com/ofuliqexey.cpp – Zabuzard Aug 09 '20 at 07:50
  • @PeterT Hmm no I didn't, good point...don't quite understand what it means I need to do to get it to work though? Is there another way to access all items for iteration since I can access a single item safely with `get()`. – Hasen Aug 09 '20 at 07:50
  • @Hasen This is the only constructor that really really creates a `new T[...]` by using reflection. And not a `new Object[...]` that is just cast to `(T[])`. `T[]` itself is erased at runtime to `Object[]`. So the only way to actually have this working at runtime is if the array is actually really a `T[]` (whatever T is at runtime), which you can only achieve with via reflection. – Zabuzard Aug 09 '20 at 07:51
  • @Zabuzard Why is it `get(0)` works fine while `items[0]` doesn't? – Hasen Aug 09 '20 at 07:53
  • Because it takes `get(T item)` and `T` is erased at runtime to `Object` as well. So at compile-time it is safe and checks the `T` and at runtime it takes all `Object`s, hence it fits into a `Object[]`. – Zabuzard Aug 09 '20 at 07:54
  • @Zabuzard But it's `public T get (int index)`. Then it returns `items[index]`. If I make a new instance of `MyArray` I can iterate through it no problem, but I can't iterate through it inside the class...although I can get individual items with `get(0)`....it's strange. – Hasen Aug 09 '20 at 07:55
  • It returns `Object` and all its users are replaced by casts to `(MyClass) get(index)`. The cast is considered safe because Java is smart enough here. But not smart enough for your direct access. You would probably have to dig into the JLS to find the exact details and rules. – Zabuzard Aug 09 '20 at 07:58
  • @Zabuzard I guess a workaround would be to iterate through with a `for i` loop using `get(i)`. It works indeed but I have to use `size` in the for loop rather than `items.length`. Why do I get the same `cannot be cast to class` error on `items.length` though? – Hasen Aug 09 '20 at 08:01
  • For the same very technical reason. Your child class thinks its a `MyClass[]` but at runtime Java can not ensure that anymore. So using `items` in any way would immediatly trigger _"Hey, why is this `Object[]`, I expected a `MyClass[]`?!"_ – Zabuzard Aug 09 '20 at 08:09
  • Does this answer your question? [Generics at Runtime](https://stackoverflow.com/questions/41439041/generics-at-runtime) – dan1st Aug 09 '20 at 08:11
  • @dan1st I would say this is a very specific edge case of the system that shouldnt be directed to that duplicate. OP would really benefit of someone digging into the JLS and finding the very exact reason for this particular issue. For example `items.length` already triggering it. So the pure fact that the child class expects a `MyClass[]` through the interface of its parent but gets a `Object[]` at runtime. I mean, it actually compiles, its a runtime issue. So the issue is that the generic-safety can not be maintained over the inheritance bound at runtime, I do not know the precise reason. – Zabuzard Aug 09 '20 at 08:13

1 Answers1

3

Explanation

The underlying cause of this is that, in Java, generics are erased at runtime. So the T[] items of LibGDX, after compilation, is just a plain Object[].

So while your code actually does compile because you used the correct type, when you run it, Java detects a possible issue because you are trying to treat Objects as MyClass. Since, again, at runtime, the array is just a Object[] and all its contents are Object. So the generics cant be kept alive during runtime.


Reflection

The only way to actually have a true T[] is to create it dynamically via reflection with the actual real type, given as token. LibGDX offers a constructor for that:

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

That way, the array will also at runtime be of type T[]. It actually also commented this in the 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

The get call works because in this situation Java is smart enough to figure out that the erasure is actually safe. So while MyClass foo = get(index); indeed decays to MyClass foo = (MyClass) get(index);, Java knows that this is a safe cast.

In your example where you use the array directly, Java can not figure that out and fails. For the exact details you probably have to dig into the JLS.


items.length

Using items in any way in your child class will immediatly trigger the problem, so even this innocent looking snippet:

int size = items.length;

This is a very technical edge case and limitation of Javas generic system. Your child class expects a MyClass[] when using items but it gets an Object[] at runtime, which is not what it wants. So it triggers the error.


Another example

Here is another minimal example that reproduces the issue without using any LibGDX. You can easily reproduce the situation as seen.

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
        }
    }
}
Zabuzard
  • 25,064
  • 8
  • 58
  • 82
  • This doesn't explain why `items.length` also throws the `cannot be cast to class` error. – Hasen Aug 09 '20 at 08:05
  • @Hasen added a section – Zabuzard Aug 09 '20 at 08:11
  • @Hasen Your welcome. If you spend a couple of hours reading through the JLS you will probably find the exact precise reason why this is happening. Not sure if that is worth it though - it is just a very specific technical limitation of Javas generic system. I did not even know this before today, especially the `items.length` is very interesting. – Zabuzard Aug 09 '20 at 08:19
  • 1
    Yeah it is strange. But I have a workaround which does the job too so it's ok at least. – Hasen Aug 09 '20 at 08:28