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.
- Signo de dólar ($) después del nombre de clase de actividad en registros de Android (clase interna no anónima)
- spinner con selección vacía
- Java.lang.ClassNotFoundException en dalvik.system.BaseDexClassLoader.findClass
- ¿Puedo estar seguro de que la aplicación Android terminó de ejecutarse comprobando isFinish () en OnPause () de la actividad principal?
- Registro de escritura de Android en el archivo
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)
- Cómo establecer TimeZone en android
- Agregar sombra al ShapeDrawable mediante programación
- ¿Por qué sólo el subproceso de interfaz de usuario en Android puede actualizar la interfaz de usuario?
- Cómo administrar adecuadamente contextos para una aplicación de Android (clases sin actividad)
- Mensaje "_Problem Loading Widget"
- Cómo centralizar el permiso de tiempo de ejecución cuando tenemos varias actividades
- RuntimeException causada debido a la excepción de seguridad
- Android - Cómo mostrar un spinner con valor pero mostrar otro diferente
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() {
- El navegador de Android no maneja correctamente la nueva línea (ASCII x0A)
- Cambiar elementos <layer-item> por código Java