Generics en Kotlin: para qué sirven y cómo usarlos

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 a los Generics en Kotlin



Los Generics (tipos genéricos) son una de las capacidades clave de los lenguajes de programación estáticamente tipados, incluyendo Kotlin. Permiten escribir código flexible y reutilizable que funciona con diferentes tipos de datos, manteniendo al mismo tiempo una estricta seguridad de tipos en tiempo de compilación.



Imagina que necesitas crear una estructura de datos para almacenar una lista de elementos. Sin generics, tendrías que escribir clases separadas para cada tipo (IntList, StringList, UserList). Con generics, una sola clase MyList<T> es suficiente, y funcionará con cualquier tipo.



// Sin Generics — hay que duplicar códigoclass IntList(private val items: MutableList<Int>)class StringList(private val items: MutableList<String>)

// Con Generics — una clase para todos los tiposclass GenericList<T>(private val items: MutableList<T>) { fun add(item: T) { items.add(item) } fun get(index: Int): T = items[index]}


En este artículo, analizaremos en detalle cómo funcionan los generics en Kotlin, veremos su sintaxis, casos de uso y conceptos avanzados como la covarianza y la contravarianza.



Fundamentos del trabajo con Generics



Declaración de clases y funciones genéricas



Para declarar una clase genérica, basta con indicar uno o varios parámetros de tipo entre corchetes angulares después del nombre de la clase:



class Box<T>(var value: T) {    fun getValue(): T = value    fun setValue(newValue: T) { value = newValue }}

// Usoval intBox = Box<Int>(42)val stringBox = Box<String>("Hello")

// Kotlin puede inferir el tipo, por lo que se puede escribir de forma más cortaval anotherBox = Box("Auto type inference")


Las funciones también pueden ser genéricas. El parámetro de tipo se indica antes del nombre de la función:



fun <T> printItem(item: T) {    println("Item: $item")}

fun <T> createPair(first: T, second: T): Pair<T, T> { return Pair(first, second)}

// UsoprintItem(123)printItem("Text")val pair = createPair("A", "B")


Restricciones de tipo (Type Bounds)



A veces es necesario restringir los tipos que se pueden usar en los generics. Para ello se utilizan límites superiores (upper bounds) con la palabra clave : Type:



// Solo tipos que implementan Comparablefun <T : Comparable<T>> findMax(a: T, b: T): T {    return if (a > b) a else b}

// Múltiples restricciones mediante wherefun <T> processItem(item: T) where T : CharSequence, T : Comparable<T> { println("Length: ${item.length}") println("Compared: ${item.compareTo(item)}")}

// Usoprintln(findMax(10, 20)) // 20println(findMax("Apple", "Banana")) // "Banana"// findMax(10, "Text") // ¡Error de compilación!


Varianza: in, out y proyección de tipos



Uno de los temas más complejos pero importantes en los generics es la varianza (variance). Define cómo se relacionan los tipos genéricos entre sí durante la herencia.



Invarianza (por defecto)



Por defecto, los generics en Kotlin son invariantes. Esto significa que Box<Number> y Box<Int> no son subtipos entre sí, incluso si Int es un subtipo de Number:



val intBox: Box<Int> = Box(42)// val numBox: Box<Number> = intBox // ¡Error de compilación!


Covarianza (out)



El modificador out hace que el tipo sea covariante. Esto significa que Box<Int> se convierte en un subtipo de Box<Number>. Se usa cuando el tipo T solo se utiliza en posiciones de salida (valores de retorno):



class Producer<out T>(private val item: T) {    fun produce(): T = item  // T solo en la salida}                

Blogs

Book Recommendations