Generic en Kotlin: cómo dejar de temer a los corchetes angulares y escribir código limpio

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

Introducción: por qué los genéricos no dan miedo, sino que son prácticos

\n\n

Imagine que está escribiendo una clase «Caja». En ella se puede poner cualquier cosa: un libro, una manzana, un teléfono. Pero si la hace para Object, cada vez que saque algo tendrá que hacer as? Book y rezar para no caer en un ClassCastException. ¿Le suena?

\n\n

Aquí es donde los generics (genéricos) vienen al rescate: un potente mecanismo de tipificación en Kotlin que permite escribir código flexible sin sacrificar la seguridad de tipos. En este artículo, exploraremos los generics en Kotlin de lo simple a lo complejo: clases básicas, funciones, varianza (in/out), tipos reificados y proyecciones de estrella. Aprenderá cómo hacer el código más limpio y al compilador su mejor aliado.

\n\n

El artículo está preparado para el portal educativo pythonlib.ru — su guía en el mundo de los lenguajes de programación modernos.

\n\n

1. Fundamentos: ¿qué son los generics en Kotlin?

\n\n

Generics (tipos genéricos) permiten parametrizar clases, interfaces y funciones con un tipo que se especificará al usarlos. Esto evita la duplicación de código y brinda control sobre los tipos en tiempo de compilación.

\n\n

Vea un ejemplo sencillo:

\n\n
class Box<T>(val item: T)\n\nfun main() {\n    val bookBox = Box<String>("Kotlin in Action")\n    val intBox = Box<Int>(42)\n    // ¡El compilador conoce el tipo!\n    val title: String = bookBox.item\n}
\n\n

Puntos clave:

\n
    \n
  • <T> — parámetro de tipo (puede ser cualquier letra, pero por convención T, R, E, K, V).
  • \n
  • El tipo se fija al crear el objeto — sin casts en tiempo de ejecución.
  • \n
  • Los genéricos solo funcionan en tiempo de compilación (type erasure), pero Kotlin añade «reificación» (más sobre esto después).
  • \n
\n\n

Funciones genéricas

\n

La tipificación en Kotlin también funciona para funciones. Por ejemplo, una función que devuelve el primer elemento de una lista:

\n\n
fun <T> firstItem(list: List<T>): T? = list.firstOrNull()\n\nfun main() {\n    val numbers = listOf(1, 2, 3)\n    val first: Int? = firstItem(numbers) // T = Int\n    println(first) // 1\n}
\n\n

Note: Kotlin puede inferir el tipo del contexto — no es necesario escribir firstItem<Int>(numbers).

\n\n

2. Varianza: in y out — cómo no romper la seguridad de tipos

\n\n

El tema más complejo para principiantes es la varianza (variabilidad). Responde a la pregunta: ¿puede List<String> ser un subtipo de List<Any>?

\n\n

En Java, esto se resuelve con wildcards (? extends / ? super). En Kotlin, con out e in.

\n\n

out (covarianza) — solo lectura

\n

La marca out significa que el tipo genérico solo puede «entregarse» (devolverse desde métodos). Ejemplo: List<out T>.

\n\n
interface Source<out T> {\n    fun next(): T  // OK: T en salida\n    // fun add(item: T) // ¡Error! T en entrada prohibido\n}\n\nfun demo(source: Source<String>) {\n    val anySource: Source<Any> = source // out lo permite\n}
\n\n

¿Por qué es seguro? Si solo podemos leer String de la fuente, entonces se puede considerar una fuente de Any.

\n\n

in (contravarianza) — solo escritura

\n

in es lo contrario: el tipo solo puede estar en la entrada. Clásico: Comparable.

\n\n
interface Comparable<in T> {\n    operator fun compareTo(other: T): Int\n}\n\nfun demo(comp: Comparable<Number>) {\n    val intComp: Comparable<Int> = comp // in lo permite\n}
\n\n

Si Comparable<Number> puede comparar cualquier número, entonces ciertamente puede comparar también Int.

\n\n

in out (invarianza) — por defecto

\n

Si no se especifica ni in ni out, el tipo es inv

Blogs

Book Recommendations