Cast No Verificado en Kotlin: Cómo convertir de forma segura List<Any?> a List<Waypoint>

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

«Tipos incompatibles»: ¿por qué Kotlin grita a tu List?

\n\n

¿Alguna vez te has encontrado con una situación en la que Kotlin, ese lenguaje «inteligente» y «seguro», de repente te muestra una advertencia amarilla Unchecked Cast: kotlin.collections.List<Kotlin.Any?> to kotlin.collections.List<Waypoint>? Si es así, no estás solo. Esta pregunta en Stack Overflow ha acumulado miles de visitas, y no es de extrañar. Cualquiera que venga de Java o empiece a trabajar más profundamente con genéricos en Kotlin, tarde o temprano se topa con este «rastrillo».

\n\n

En este artículo no solo daremos una explicación superficial. Analizaremos por qué sucede esto, mostraremos tres formas de resolver el problema correctamente y, lo más importante, cómo escribir código que no explote en tiempo de ejecución. ¿Listos? ¡Allá vamos!

\n\n

El núcleo del problema: Type Erasure y «borrado» de tipos

\n\n

Vamos directo al grano. Imagina que tienes una función que devuelve una lista de cualquier cosa — List<Any?>. Pero tú necesitas una lista de objetos concretos, por ejemplo, List<Waypoint>. La solución más «simple» que viene a la mente es la conversión explícita:

\n\n
val rawList: List<Any?> = getDataFromSomewhere() // Supongamos que es JSON o una base de datos\nval waypointList: List<Waypoint> = rawList as List<Waypoint> // Unchecked Cast! ⚠️
\n\n

Kotlin (y Java bajo el capó) utiliza el mecanismo de Type Erasure (borrado de tipos). Esto significa que durante la ejecución del programa, la información sobre el tipo dentro de los genéricos (lo que está entre los corchetes angulares) se borra. Para la JVM, tu List<Waypoint> es simplemente List. El compilador ve que intentas engañar al sistema y muestra una advertencia: «Oye, no puedo verificar que realmente haya Waypoint allí, ¡tú asumes la responsabilidad!».

\n\n

Si estás 100% seguro de que solo contiene Waypoint, puedes suprimir la advertencia con la anotación @Suppress("UNCHECKED_CAST"). Pero si resulta que hay, digamos, un String, obtendrás un ClassCastException en tiempo de ejecución. Y eso es exactamente de lo que Kotlin intenta protegernos.

\n\n

Cómo NO hacerlo: errores típicos de principiantes

\n\n

Aquí hay algunos ejemplos que veo a menudo en los foros. Recuérdalos para no repetirlos:

\n
    \n
  • Conversión ciega: list as List<Waypoint> — la opción más peligrosa.
  • \n
  • Ignorar la advertencia: «¡Funciona!» — funciona hasta que falla.
  • \n
  • Intentar convertir en cadena: (list as? List<Waypoint>) — el operador seguro as? no ayudará si dentro de la lista hay objetos incorrectos. Solo verificará que sea un List, pero no su contenido.
  • \n
\n\n

3 formas prácticas: de lo simple a lo profesional

\n\n

Veamos ejemplos de código reales. Supongamos que tenemos una clase Waypoint:

\n\n
data class Waypoint(val latitude: Double, val longitude: Double, val name: String)
\n\n

Y tenemos una lista «sucia» obtenida de una API externa:

\n\n
fun fetchRawData(): List<Any?> {\n    return listOf(\n        mapOf("lat" to 55.75, "lon" to 37.62, "name" to "Moscow"),\n        mapOf("lat" to 59.93, "lon" to 30.31, "name" to "SPb")\n    )\n}
\n\n

Forma 1: Filtrado con filterIsInstance (Recomendada ✅)

\n\n

Esta es la forma más idiomática y segura. La función filterIsInstance<T>() filtrará solo aquellos elementos que sean instancias del tipo especificado y devolverá una lista ya tipificada.

\n\n
fun getWaypointsSafely(): List<Waypoint> {\n    val rawData: List<Any?> = fetchRawData()\n    // Supongamos que de alguna manera convertimos Map en Waypoint\n    val waypoints: List<Waypoint> = rawData\n        .filterIsInstance<Map<*, *>>()\n                

Blogs

Book Recommendations