Extraña java.lang.ClassCastException al usar la llamada

Estoy usando la biblioteca LibDGX para mi juego. Y me enfrenté a la exception común ClassCastException , pero ocurre en casos extraños.

Estoy usando la class de Animación de la biblioteca LibGDX.

Estoy recibiendo un error en esta línea solo si estoy usando la operación [] .

 val tex = animation.keyFrames[0] 

Si lo cambio a get() error desaparece .

 tex = animation.keyFrames.get(0) 

Aquí está el método completo. Yo simplifiqué el ejemplo.

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

Extraño, ¿verdad?

Aquí está la class de animation que utilicé para minimizar otros motivos de error. Es lo mismo que en 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; } } 

También noté que el error desaparece si utilizo Array.with construction en lugar de crear Array<TextureRegion> Me refiero a LibGDX Array no de Kotlin.

 val animation = Animation(Array.with(TextureRegion(Texture("badlogic.jpg")))) 

¿Podría decirme por qué ocurre esto o es posible que haya un error?

Editar : en versiones anteriores de libGDX, Animation no usa las capacidades de reflexión de Array . Si lo hubiera hecho , no tendrías este error, ¡porque la matriz sería del tipo correcto! Debería estar en libGDX en la versión 1.9.7. Nota: debe especificar la Class de la Array al crearla.

Esto fue arreglado recientemente en # 4476 . Sin embargo, el siguiente "error" con get y [] da como resultado un bytecode diferente para una matriz aún puede valer la pena informar / preguntar acerca de.


¡Esto es interesante!

Descomstackndo

 val tex1 = animation.keyFrames.get(0) 

da

 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

mientras descomstack

 val tex = animation.keyFrames[0] 

da

 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

Es importante tener en count que el tipo de keyFrames es T[] , por lo que no es posible que tenga un método get personalizado (suponiendo que no existan extensiones).

Parece que aunque los dos deberían ser iguales ( [] invoca la operator fun get ), ¡no lo son!

La única diferencia que puedo ver es que la variante get comtesting el tipo ( CHECKCAST ) después de recuperarlo a través de AALOAD , mientras que la variante [] intenta comprobar que el tipo de la matriz es T[] inmediatamente después de recuperar la matriz.

Sin embargo, debido a que los frames se declaran en el constructor de Animation :

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

como

 T[] frames = (T[]) new Object[keyFrames.size]; 

¡el tipo real de la matriz es Object[] ! Así que este lanzamiento a TextureRegion[] falla.

Para mí, esto parece un error libGDX.


Puedo reproducir esto muy fácilmente con una class simple en 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] } 

Esto también arrojará una ClassCastException en la línea con [] ! Además, el bytecode tiene la misma diferencia.


El problema realmente surge con la falta de matrices genéricas en Java (y Kotlin). Hay dos soluciones principales para esto en Java:

  • Almacene un Object[] y eche los valores al get
  • Almacenar un Object[] , pero lanzarlo a T[]

Esto es un problema, porque el tipo real de la matriz es Object[] . ¡Como consecuencia, estas matrices "genéricas" no deberían exponerse! Puedo entender por qué libGDX lo hace, es decir, para mejorar el performance y eliminar la pequeña sobrecarga de llamadas a methods, pero esto definitivamente no es seguro.


Sin embargo, el hecho de que get y [] difieran puede ser un error. Tal vez investigar esto o presentar un informe de error puede ayudar.


Lista de instrucciones de bytecode de Java