Generics en Kotlin: para qué sirven y cómo usarlos en la práctica

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

Generics en Kotlin: para qué sirven y cómo usarlos en la práctica



Los Generics (genéricos) son una de las características clave del lenguaje Kotlin que permite escribir código más flexible, type-safe y reutilizable. Si alguna vez has escrito List<String> o Map<Int, User>, ya te has encontrado con los genéricos. Pero, ¿para qué sirven realmente y cómo sacarles el máximo partido? En este artículo analizaremos la teoría y pasaremos a la práctica con ejemplos reales.



El problema que resuelven los Generics



Imagina que necesitas escribir una función que devuelva el primer elemento de una lista. Sin genéricos, tendrías que escribir una función separada para cada tipo de dato:



fun getFirstString(list: List<String>): String? = list.firstOrNull()fun getFirstInt(list: List<Int>): Int? = list.firstOrNull()fun getFirstUser(list: List<User>): User? = list.firstOrNull()


Esto provoca duplicación de código. Usar Any resolvería el problema, pero perderías información sobre el tipo en tiempo de compilación:



fun getFirst(list: List<Any>): Any? = list.firstOrNull()

// Habría que hacer casting de tiposval name: String? = getFirst(strings) as? String


Los Generics resuelven este problema de forma elegante:



fun <T> getFirst(list: List<T>): T? = list.firstOrNull()

// ¡El compilador conoce el tipo!val name: String? = getFirst(listOf("Alice", "Bob"))val age: Int? = getFirst(listOf(25, 30))


Ahora una sola función funciona con cualquier tipo, manteniendo total type-safety.



Fundamentos de Generics en Kotlin: sintaxis y declaración



Los genéricos se declaran usando corchetes angulares <T>. La letra T es solo una convención; se pueden usar cualquier nombre: E (elemento), K (clave), V (valor).



Clases Genéricas



class Box<T>(val value: T) {    fun getContent(): T = value}

fun main() { val stringBox = Box("Hello") val intBox = Box(42) println(stringBox.getContent()) // Hello println(intBox.getContent()) // 42}


Funciones Genéricas



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

// Se puede especificar el tipo explícitamenteprintItem<String>("Kotlin")// O confiar en la inferencia de tiposprintItem(100)


Interfaces Genéricas



interface Repository<T> {    fun getById(id: Int): T    fun save(item: T): Boolean}

class UserRepository : Repository<User> { override fun getById(id: Int): User { /* ... */ } override fun save(item: User): Boolean { /* ... */ }}


Varianza: in, out y el asterisco *



Una de las características más potentes de Kotlin es la varianza. Responde a la pregunta: ¿puede List<String> ser un subtipo de List<Any>?



Covarianza (out)



Usa out cuando tu tipo genérico solo se devuelve (producer). Por ejemplo, List<T> es covariante:



// Puedes asignar List<String> a una variable List<Any>val strings: List<String> = listOf("a", "b")val anyList: List<Any> = strings // ¡OK!

// Pero no puedes agregar elementos// anyList.add(1) // ¡Error de compilación!


Ejemplo propio:



class Producer<out T>(private val value: T) {    fun produce(): T = value}

fun main() { val stringProducer: Producer<String> = Producer("Hello") val anyProducer: Producer<Any> = stringProducer // ¡Funciona! println(anyProducer.produce())}


Contravarianza (in)



Usa in cuando el tipo solo se recibe (consumer). Por ejemplo, Comparable<T>:





                
                

Blogs

Book Recommendations