ClassCastException mientras usa varargs y genéricos

Estoy usando java genéricos y varargs.

Si utilizo el siguiente código, obtendré una ClassCastException , aunque no esté usando moldes en absoluto.

Más extraño aún, si ejecuto esto en Android (dalvik) no se incluye ningún rastro de la pila con la excepción, y si cambio la interfaz a la 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 esto, obtendrá un ClassCastException que Object no se puede convertir en String con traza de pila que apunta a un número de línea no válido. ¿Se trata de un error en Java? Lo he probado en Java 7 y en la API de Android 8. Hice una solución para ello (comentado en el método doStuff ), 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) 

Este es el comportamiento esperado. Cuando se utilizan 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, varargs se convierten en matrices, y cuando se llama a un método varargs genérico, Java crea una matriz del 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 molde invisible en su código para reforzar este tipo, y debido a que Object[] no se puede convertir en String[] , obtendrá una excepción. El número de línea falso que ves es presumiblemente porque el reparto no aparece en ninguna parte de tu código.

Una solución para esto es quitar completamente los genéricos de la interfaz y la clase de devolución de llamada, reemplazando todos los tipos por Object .

La respuesta de grahamparks es correcta. El misterioso typecast es un comportamiento normal. Se insertan por el compilador para asegurarse de que la aplicación sea de tipo runtime frente a posibles usos incorrectos de genéricos.

Si usted está jugando por las reglas, este typecast siempre tendrá éxito. Está fallando porque ha ignorado / suprimido las advertencias sobre el uso inseguro de genéricos. Esto no es una cosa sabia que hacer … especialmente si usted no entiende exactamente entender lo que significan, y si se puede ignorar con seguridad.

Eso es de hecho debido al tipo de borrado, pero la parte crítica aquí es varargs. Como ya se ha señalado, se aplican como tabla. Por lo tanto, el compilador está creando un objeto [] para empaquetar los parámetros y, por tanto, el molde posterior no válido. Pero hay un hack a su alrededor: si eres lo suficientemente agradable para pasar una tabla como vararg, el compilador lo reconocerá, no re-empacarlo y porque le ahorraste un poco de trabajo que te permitirá ejecutar tu código 🙂

Intente ejecutar después de las siguientes modificaciones:

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

y

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

  • ¿Existe realmente el Objeto Inmortal?
  • Notificación incorrecta enviada desde el paquete, no se pudo expandir RemoteViews
  • Método recursivo funciona en java con consola, pero no con android
  • Cómo verificar un mensaje de brindis en selendroid
  • ¿RestKit en android?
  • Gradle excluye la clase java de lib reemplazada por su propia clase para evitar duplicados
  • ¿Cómo puedo convertir una parte del archivo de código fuente de Java a Kotlin?
  • Cómo limpiar caché de proyecto en Intellij idea como Eclipse limpio?
  • ¿Cómo convertir la fecha en formato particular en android?
  • Retrofit-IllegalArgumentException: url inesperada
  • Android: enum vs static final ints?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.