«out» en Kotlin: analizando la covarianza con ejemplos y preguntas

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

¿Qué se esconde detrás de la palabra out en Kotlin? Respondemos a las preguntas más populares

\n\n

Si alguna vez has escrito genéricos en Kotlin y te has encontrado con un error de compilación confuso, probablemente te hayas topado con la palabra out. Este pequeño modificador genera mucho debate incluso entre desarrolladores experimentados. Hemos recopilado las preguntas más frecuentes de Stack Overflow y las hemos convertido en una guía clara. Hoy aprenderás qué es out, para qué sirve y cómo no confundirte con la covarianza.

\n\n

Aclaremos esto de una vez por todas — sin teoría aburrida, solo código vivo y práctica.

\n\n

¿Por qué Kotlin inventó out? (Breve excursión histórica)

\n\n

En Java existe wildcard — el signo de interrogación ? extends T. Significa que una colección puede contener objetos de tipo T o sus subtipos. El problema es que Java no te prohíbe poner accidentalmente un objeto incorrecto en dicha colección — solo obtendrás un error en tiempo de ejecución. Kotlin resolvió este problema a nivel de tipos.

\n\n

La palabra clave out en Kotlin es una declaración de covarianza. Si escribes Source<out T>, significa: «Puedo leer objetos de tipo T de este Source, pero no puedo escribir en él». El compilador lo verifica estrictamente.

\n\n

Pregunta 1: ¿En qué se diferencia out de in?

\n\n

La pregunta más frecuente en Stack Overflow. Imaginemos una caja:

\n\n
    \n
  • out — una caja de la que solo puedes sacar cosas (produce).
  • \n
  • in — una caja en la que solo puedes poner cosas (consume).
  • \n
\n\n

Ejemplo:

\n\n
// Covarianza: podemos leer, pero no escribir\ninterface Producer<out T> {\n    fun produce(): T\n}\n\n// Contravarianza: podemos escribir, pero no leer\ninterface Consumer<in T> {\n    fun consume(item: T)\n}\n\nfun main() {\n    val stringProducer: Producer<String> = object : Producer<String> {\n        override fun produce(): String = "Hello"\n    }\n    \n    val anyProducer: Producer<Any> = stringProducer // ¡Funciona! out lo permite\n    println(anyProducer.produce())\n}\n
\n\n

Observa: Producer<out T> permite asignar Producer<String> a una variable de tipo Producer<Any>. Sin out esto sería imposible.

\n\n

Pregunta 2: ¿Por qué Kotlin prohíbe fun set(value: T) con out?

\n\n

Esta es una trampa clásica. Si declaras out, el compilador no permitirá funciones que tomen T como parámetro. ¿Por qué?

\n\n

Imagina que tienes un MutableList<out Number>. Podrías poner un Int en él, pero el compilador no sabe qué tipo de lista es realmente. ¿Quizás es un MutableList<Double>? Si se permitiera la escritura, romperías los tipos. out garantiza seguridad: solo puedes leer.

\n\n
// ¡No compilará!\ninterface BadList<out T> {\n    fun get(index: Int): T  // OK\n    fun add(item: T)        // ¡Error! T está en posición in\n}\n
\n\n

Solución: si necesitas tanto leer como escribir, usa in y out por separado, o un tipo invariante (sin modificador).

\n\n

Pregunta 3: ¿Cómo se relaciona out con el wildcard ? extends de Java?

\n\n

Correspondencia directa: Source<out T> en Kotlin = Source<? extends T> en Java. Pero hay una diferencia importante:

\n\n
    \n
  • En Java, wildcard es una varianza en el uso (use-site variance).
  • \n
  • En Kotlin, out es una varianza en la declaración (declaration-site variance).
  • \n
\n\n

Ejemplo en Java:

\n\n
// Java: usamos wildcard al declarar la variable\nList<? extends Number> numbers = new ArrayList<Integer>();\nnumbers.a                

Blogs

Book Recommendations