5

Estoy usando java generics y varargs.ClassCastException durante el uso de varargs y genéricos

Si uso el siguiente código, obtendré un ClassCastException, aunque no estoy utilizando moldes en absoluto.

Más extraño aún, si ejecuto esto en Android (dalvik), no se incluye ningún rastro de pila con la excepción, y si cambio la interfaz a clase abstracta, la variable de excepción e está vacía.

El código:

public class GenericsTest { 
    public class Task<T> { 
     public void doStuff(T param, Callback<T> callback) { 
      // This gets called, param is String "importantStuff" 

      // Working workaround: 
      //T[] arr = (T[]) Array.newInstance(param.getClass(), 1); 
      //arr[0] = param; 
      //callback.stuffDone(arr); 

      // WARNING: Type safety: A generic array of T is created for a varargs parameter 
      callback.stuffDone(param); 
     } 
    } 

    public interface Callback<T> { 
     // WARNING: Type safety: Potential heap pollution via varargs parameter params 
     public void stuffDone(T... params); 
    } 

    public void run() { 
     Task<String> task = new Task<String>(); 
     try { 
      task.doStuff("importantStuff", new Callback<String>() { 
       public void stuffDone(String... params) { 
        // This never gets called 
        System.out.println(params); 
       }}); 
     } catch (ClassCastException e) { 
      // e contains "java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;" 
      System.out.println(e.toString()); 
     } 
    } 

    public static void main(String[] args) { 
     new GenericsTest().run(); 
    } 
} 

Si ejecuta este, obtendrá un ClassCastException que Object no se puede convertir a String con seguimiento de la pila que apunta a la línea número válido. ¿Es esto un error en Java? Lo probé en Java 7 y Android API 8. Hice una solución para esto (comentada en el doStuff -method), pero parece tonto tener que hacerlo de esta manera. Si elimino varargs (T...), todo funciona bien, pero mi implementación real lo necesita.

StackTrace de excepción es:

java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String; 
    at GenericsTest$1.stuffDone(GenericsTest.java:1) 
    at GenericsTest$Task.doStuff(GenericsTest.java:14) 
    at GenericsTest.run(GenericsTest.java:26) 
    at GenericsTest.main(GenericsTest.java:39) 
+1

¿existe una posibilidad de que podría proporcionar una copia de la StackTrace. Mi sospecha es que es del elenco implícito que ocurre debido al borrado de tipo. – Matt

+0

Se agregó rastro de pila a la pregunta. – murgo

Respuesta

9

Este es el comportamiento esperado. Cuando utiliza genéricos en Java, los tipos reales de los objetos no se incluyen en el bytecode compilado (esto se conoce como borrado de tipo). Todos los tipos se convierten en Object y los moldes se insertan en el código compilado para simular el comportamiento mecanografiado.

Además, los varargs se convierten en matrices, y cuando se llama a un método varargs genérico, Java crea una matriz de tipo Object[] con los parámetros del método antes de llamarlo.

Por lo tanto, su línea callback.stuffDone(param); compila como callback.stuffDone(new Object[] { param });. Sin embargo, su implementación de la devolución de llamada requiere una matriz de tipo String[]. El compilador de Java ha insertado un yeso invisible en su código para aplicar este tipado, y dado que Object[] no se puede convertir a String[], se obtiene una excepción. El número de línea falso que ves es presumiblemente porque el elenco no aparece en ningún lugar de tu código.

Una solución para esto es eliminar por completo los genéricos de su interfaz y clase de devolución de llamada, reemplazando todos los tipos con Object.

+0

Ok, pensé que sería eso. Es extraño que se compile con el nuevo Object [] {param}, no con el nuevo String [] {param}, que funcionaría. El compilador tiene toda la información para hacer el mejor lanzamiento (sabe lanzarlo a String [] de todos modos). Eso y el número de línea quebrado en el rastro de la pila me hicieron pensar que era un error de Java. Eliminé los varargs, guardé genéricos en mi programa real. – murgo

+0

@murgo: "El compilador tiene toda la información ..." No, no. La matriz se crea cuando llama a la función varargs, es decir, en 'doStuff', cuando llama a' callback.stuffDone (param); '. En ese lugar, no sabe, ni en tiempo de compilación ni en tiempo de ejecución, qué sería T. – newacct

0

grahamparks La respuesta es correcta. El misterioso encasillado es un comportamiento normal. El compilador los inserta para garantizar que la aplicación sea segura en tiempo de ejecución ante el posible uso incorrecto de los genéricos.

Si sigue las reglas, este tipo de conversión siempre tendrá éxito. Está fallando porque ha ignorado/suprimido las advertencias sobre el uso inseguro de los genéricos. Esto no es algo sabio de hacer ... especialmente si no comprende exactamente lo que significan, y si pueden ignorarse con seguridad.

+0

Hubo advertencias, que se muestran en la pregunta. Aún así, pensé que algo era sospechoso, porque pensé que estaría usando los genéricos de una "manera útil". Esto realmente debería ser un error de compilación en lugar de una advertencia. – murgo

+1

@murgo - Son advertencias porque hay algunos casos en los que es seguro ignorarlos. De hecho, hay casos en que la mejor solución implica ignorar/suprimir la advertencia. –

0

Eso es de hecho debido al borrado de tipo, pero la parte crítica aquí es varargs. Como ya se señaló, se implementan como tabla. Así que el compilador en realidad está creando un Objeto [] para empacar sus parámetros y, por lo tanto, un molde inválido posterior. Pero hay un truco al respecto: si eres lo suficientemente bueno como para pasar una tabla como vararg, el compilador lo reconocerá, no volverá a empacarlo y porque lo salvó un poco de trabajo le permitirá ejecutar su código :-)

intenta ejecutar después siguientes modificaciones:

public void doStuff(T[] param, Callback callback) {

y

task.doStuff(new String[]{"importantStuff"}, new Callback() {

+0

su solución comentada hace básicamente lo mismo, no lo he notado al principio ... – wmz