I'm using LibDGX library for my game. And i faced common exception ClassCastException
, but it occurs in strange case.
I'm using Animation class from LibGDX library.
I'm getting error on this line only if i am using []
operation.
val tex = animation.keyFrames[0]
If i change it to get()
error disappears.
tex = animation.keyFrames.get(0)
Here is full method. I simplified example.
override fun create() {
val frames = Array<TextureRegion>()
frames.add(TextureRegion(Texture("badlogic.jpg")))
val animation = Animation(frames)
val tex1 = animation.keyFrames.get(0) // Compiles
val tex = animation.keyFrames[0] // error - [Ljava.lang.Object; cannot be cast to [Lcom.badlogic.gdx.graphics.g2d.TextureRegion;
}
Strange, isn't it?
Here is Animation class that i used to minimise other error reasons. It is the same as in LibGDX api.
public class Animation<T> {
T[] keyFrames;
private float frameDuration;
public Animation (float frameDuration, Array<? extends T> keyFrames) {
this.frameDuration = frameDuration;
Class arrayType = keyFrames.items.getClass().getComponentType();
T[] frames = (T[]) ArrayReflection.newInstance(arrayType, keyFrames.size);
for (int i = 0, n = keyFrames.size; i < n; i++) {
frames[i] = keyFrames.get(i);
}
setKeyFrames(frames);
}
protected void setKeyFrames (T... keyFrames) {
this.keyFrames = keyFrames;
}
}
I also noticed that error disappears if i use Array.with
construction instead of creating Array<TextureRegion>
I mean LibGDX Array not from Kotlin.
val animation = Animation(Array.with(TextureRegion(Texture("badlogic.jpg"))))
Could you tell me the reason why this occurs or it is possible a bug?
Edit: In older versions of libGDX, Animation
does not use Array
's reflection capabilities. If it had, you wouldn't have this error, because the array would be of the correct type! It should be in libGDX in version 1.9.7. Note: you must specify the Class
of the Array
when creating it.
This was fixed recently in #4476.
However, the following "bug" with get
and []
resulting in different bytecode for an array may still be worth reporting/inquiring about.
This is interesting!
Decompiling
val tex1 = animation.keyFrames.get(0)
gives
ALOAD 2
INVOKEVIRTUAL com/badlogic/gdx/graphics/g2d/Animation.getKeyFrames ()[Ljava/lang/Object;
ICONST_0
AALOAD
CHECKCAST com/badlogic/gdx/graphics/g2d/TextureRegion
ASTORE 3
while decompiling
val tex = animation.keyFrames[0]
gives
ALOAD 2
INVOKEVIRTUAL com/badlogic/gdx/graphics/g2d/Animation.getKeyFrames ()[Ljava/lang/Object;
CHECKCAST [Lcom/badlogic/gdx/graphics/g2d/TextureRegion;
ICONST_0
AALOAD
ASTORE 4
It is important to note that the type of keyFrames
is T[]
, so it cannot possibly have a custom get
method (assuming no extensions exist.)
It seems that although the two should be equal ([]
invokes operator fun get
), they are not!
The only difference I can see is that the get
variant checks the type (CHECKCAST
) after retrieving it via AALOAD
, while the []
variant attempts to check that the type of the array is T[]
immediately after retrieving the array.
However, because frames
is declared in Animation
's constructor:
public Animation (float frameDuration, Array<? extends T> keyFrames) {
this.frameDuration = frameDuration;
T[] frames = (T[]) new Object[keyFrames.size];
for (int i = 0, n = keyFrames.size; i < n; i++) {
frames[i] = keyFrames.get(i);
}
setKeyFrames(frames);
}
as
T[] frames = (T[]) new Object[keyFrames.size];
the actual type of the array is Object[]
! So this cast to TextureRegion[]
fails.
To me, this seems like a libGDX bug.
I am able to very easily reproduce this with a simple class in Java:
public class JavaObjWithArray<T> {
private T[] items;
public JavaObjWithArray(T[] items) {
this.items = (T[]) new Object[items.length];
System.arraycopy(items, 0, this.items, 0, items.length);
}
public T[] getItems() {
return items;
}
}
fun main(args: Array<String>) {
val obj = JavaObjWithArray(arrayOf("a", "b"))
val one = obj.items.get(0)
val two = obj.items[0]
}
This will also throw a ClassCastException
on the line with []
! Additionally, the bytecode has the same difference.
The problem really arises with the lack of generic arrays in Java (and Kotlin). There are two main solutions for this in Java:
- Store an
Object[]
and cast the values upon get
- Store an
Object[]
, but cast it to T[]
This is a problem - because the actual type of the array is Object[]
. As a consequence, these "generic" arrays should not be exposed! I can understand why libGDX does so, namely, to improve performance and remove the small overhead of method calls, but this is definitely not safe.
The fact that get
and []
differ may be a bug, though. Perhaps looking into this or filing a bug report may help.
Java bytecode instruction list