En este articulo revisamos conceptos clave y preguntas practicas sobre genericos en Kotlin, traducido y adaptado al español para facilitar su comprension.
Los genericos en Kotlin permiten escribir clases, interfaces y funciones con un marcador de tipo para crear codigo reutilizable y seguro en tiempo de compilacion que funciona con diferentes tipos.
Existen dos enfoques para la varianza: declaration-site variance en la propia declaracion de la clase o interfaz y use-site variance mediante proyecciones de tipo en las firmas de funciones.
Palabras clave fundamentales
out indica que el tipo es un productor y puede devolver valores del tipo T.
in indica que el tipo es un consumidor y puede aceptar valores del tipo T.
where permite definir varias restricciones sobre un parametro generico.
reified conserva informacion de tipo en tiempo de ejecucion en funciones inline, permitiendo comprobaciones seguras con is y conversiones con as.
Any? es el supertipo de todos los tipos. Nothing es el subtipo final de todos los tipos.
Ejemplo de interfaz productora: interface Source<out T> { fun nextT(): T }
Covarianza: declarada con out, permite que una clase parametrizada sea considerada productora del tipo. Es equivalente a Java C<? extends Base> y un ejemplo comun es List.
Contravarianza: declarada con in, convierte el parametro en consumidor. Un ejemplo tipico es Comparable con la firma interface Comparable<in T> { operator fun compareTo(other: T): Int }
Invarianza: algunas clases aceptan y producen el mismo tipo, por eso no pueden ser ni co ni contravariantes. Un ejemplo es Array.
Type erasure: en tiempo de ejecucion las instancias genericas no retienen informacion del parametro T para asegurar interoperabilidad con JVM antiguas.
Star projections resuelven la seguridad de tipo cuando se desconoce el argumento generico. Para una interfaz Function<in T, out U>:
Function<*, String> equivale a Function<in Nothing, String>.
Function<Int, *> equivale a Function<Int, out Any?>.
Function<*, *> equivale a Function<in Nothing, out Any?>.
Para declarar que un T generico no puede ser nulo se usa T & Any. El caracter guion bajo puede utilizarse para que el compilador infiera automaticamente un argumento de tipo cuando otros estan especificados.
Preguntas y respuestas practicas
Pregunta 1: Por que no esta permitido hacer esto: class Box<T>(val value: T) fun takeBox(box: Box<Any>) { println(box.value) } fun main() { val strBox = Box('Hello') takeBox(strBox) }
Respuesta: Los genericos son invariante por defecto. Aunque Int es subtipo de Any, Box<Int> no es subtipo de Box<Any>, por eso la llamada no compila.
Pregunta 2: Cual seria la salida del codigo fun <T> isString(value: T): Boolean { return value is String } fun main() { println(isString('Hello')) println(isString(42)) }
Respuesta: Imprimira true y luego false. Aunque T esta sujeto a type erasure, la comprobacion is se aplica sobre el valor concreto en tiempo de ejecucion.
Pregunta 3: Que significa esto: fun <T> onlyNumbers(a: T, b: T): T where T : Number, T : Comparable<T> { return if (a > b) a else b }
Respuesta: T debe ser un Number y ademas implementar Comparable de si mismo. where se utiliza para indicar multiples restricciones sobre T.
Pregunta 4: Por que no compila este ejemplo: fun copy(first: Array<out Any>, second: Array<Any>) { for (i in first.indices) { first[i] = second[i] } } fun main() { val strings = arrayOf('A', 'B', 'C') val anys = Array<Any>(3) { '' } copy(strings, anys) }
Respuesta: El parametro first esta declarado con out y por tanto es un productor solo, no puede aceptar valores via asignacion first[i] = ...
Pregunta 5: Compilara esto: class Box<T>(val value: T) { fun <T> printTwice(t: T) { println('$value $t') } } fun main() { val box = Box('Kotlin') box.printTwice(42) }
Respuesta: Si compila. El parametro generico de la funcion printTwice sombreado resulta en un nuevo T local a la funcion. Imprimira Kotlin 42.
Pregunta 6: Compilara este bloque: fun fill(list: MutableList<out Number>) { list.add(42) }
Respuesta: No compila. MutableList<out Number> es productor y por tanto no puede recibir elementos mediante add.
Pregunta 7: Por que esto no funciona: fun mystery(list: MutableList<*>) { list.add(null) }
Respuesta: MutableList<*> es una proyeccion estrella que equivale a MutableList<out Any?>, es tratada como productora y no admite insercion de elementos no nulos. Solo se permite añadir null si la lista lo admite y la firma lo permite, pero en general la proyeccion impide add.
Pregunta 8: Por que no esta permitido esto: fun <T : Number> sum(a: T, b: T): T { return a + b }
Respuesta: El operador plus no esta definido en la clase Number, sino en subtipos concretos como Int o Double. Para sumar genericos de Number es necesario convertir o trabajar con operaciones definidas en subtipos o proporcionar una estrategia de suma.
Pregunta 9: Cual sera la salida de este codigo con reified: inline fun <reified T> check(value: Any) { println(value is T) } fun main() { check<List<String>>(listOf('a', 'b')) check<List<Int>>(listOf('a', 'b')) }
Respuesta: Imprimira true y true. reified conserva la informacion del tipo top level List, pero no evita el type erasure de los parametros internos, asi que ambas comprobaciones ven solo que value es una List.
Pregunta 10: Por que no se permite esto: object Cache<T> { private val items = mutableListOf<T>() }
Respuesta: object define un singleton con una unica instancia; un singleton no puede ser parametrizado con tipos distintos en tiempo de ejecucion, por eso no se permiten genericos en object.
Pregunta 11: Para que sirve esta declaracion: interface ComparableSelf<T : ComparableSelf<T>> { fun compareTo(other: T): Int }
Respuesta: Obliga a que las clases que implementen la interfaz declaren compareTo que reciba su propio tipo, garantizando comparabilidad estricta entre instancias del mismo tipo.
Pregunta 12: Es correcta la siguiente declaracion: class Box<T : Int>(val value: T)
Respuesta: No tiene sentido porque Int es final y no puede ser subtipo. En ese caso basta declarar class Box(val value: Int).
Pregunta 13: Cual sera la salida de este ejemplo con sobrecarga de funciones: fun <T> printType(value: T) { println('Generic') } fun printType(value: String) { println('String') } fun main() { printType('Hello') }
Respuesta: Imprimira String. El compilador elige la funcion mas especifica que coincide con los argumentos.
Pregunta 14: Cual sera la salida usando reified para obtener la clase: inline fun <reified T> printClass(list: List<T>) { println(T::class.java) } fun main() { printClass(listOf('A', 'B')) printClass(listOf(1, 2, 3)) }
Respuesta: Mostrara class java.lang.String y class java.lang.Integer. reified preserva el tipo T en tiempo de ejecucion para este proposito.
Si quieres profundizar mas en Kotlin y genericos puedes contactarnos para formacion, auditorias de codigo y migraciones de proyectos.
Sobre Q2BSTUDIO
Q2BSTUDIO es una empresa de desarrollo de software y aplicaciones a medida especializada en soluciones empresariales modernas. Ofrecemos software a medida, aplicaciones a medida, proyectos de inteligencia artificial y servicios de ciberseguridad. Ademas trabajamos con servicios cloud aws y azure para desplegar arquitecturas escalables y seguras.
Nuestros servicios incluyen servicios inteligencia de negocio, implementacion de power bi, ia para empresas, agentes IA y soluciones personalizadas de machine learning. Combinamos experiencia en desarrollo de aplicaciones a medida y software a medida con practicas de ciberseguridad y operaciones en la nube para ofrecer productos listos para produccion.
Si buscas potenciar datos con inteligencia artificial, monitorizar seguridad, migrar a servicios cloud aws y azure o desplegar soluciones con power bi y agentes IA, Q2BSTUDIO puede ayudarte a diseñar e implementar la solucion adecuada.
Palabras clave para posicionamiento: aplicaciones a medida, software a medida, inteligencia artificial, ciberseguridad, servicios cloud aws y azure, servicios inteligencia de negocio, ia para empresas, agentes IA, power bi.
Contacta con Q2BSTUDIO para consultar proyectos de desarrollo de aplicaciones a medida y soluciones de inteligencia artificial que aporten valor real a tu negocio.