0Pricing
Kotlin Academy · Lección

Varianza en el sitio de declaración: in y out

Use correctamente parámetros de tipo covariantes (out) y contravariantes (in).

Varianza en el sitio de declaración: in y out es una lección gratuita de Kotlin Academy en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Kotlin Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Kotlin Academy incluye 4 lecciones en total.

El problema de la varianza

En Kotlin, List<String> es un subtipo de List<Any> porque List se declara con out T. Sin las anotaciones de varianza, esto no se cumpliría.

val strings: List<String> = listOf("a", "b")
val anys: List<Any> = strings  // OK because List<out T>
// MutableList<String> is NOT a subtype of MutableList<Any>:
// val m: MutableList<Any> = mutableListOf("x") // compile error
fun main() { println(anys) }

Covarianza con out

out T significa que la clase solo puede producir valores T (devolverlos), nunca consumirlos. Esto hace que Producer<Dog> sea un subtipo de Producer<Animal>.

interface Producer<out T> {
    fun produce(): T
}
class DogProducer : Producer<String> {
    override fun produce() = "Woof!"
}
fun sound(p: Producer<Any>) = println(p.produce())
fun main() {
    val dog = DogProducer()
    sound(dog)  // OK: Producer<String> is a subtype of Producer<Any>
}

Contravarianza con in

in T significa que la clase solo puede consumir valores T (aceptarlos como parámetros), nunca producirlos. Esto hace que Consumer<Animal> sea un subtipo de Consumer<Dog>.

interface Consumer<in T> {
    fun consume(item: T)
}
class Printer : Consumer<Any> {
    override fun consume(item: Any) = println(item)
}
fun feedDog(consumer: Consumer<String>) = consumer.consume("Dog")
fun main() {
    val printer = Printer()
    feedDog(printer)  // OK: Consumer<Any> is a subtype of Consumer<String>
}

Restricción de out: no usar en posición in

Con out T, T no puede aparecer en la posición de parámetro (in). El compilador lo impone.

interface ReadOnly<out T> {
    fun get(): T          // OK: out position
    // fun set(t: T) {}  // Error: T in in-position violates out variance
}
class Box<out T>(private val value: T) : ReadOnly<T> {
    override fun get() = value
}
fun main() {
    val box: ReadOnly<Any> = Box<String>("hello")
    println(box.get())
}

Restricción de in: no usar en posición out

Con in T, T no puede aparecer en la posición de retorno (out).

interface WriteOnly<in T> {
    fun set(t: T)        // OK: in position
    // fun get(): T {}  // Error: T in out-position violates in variance
}
class Sink<in T> : WriteOnly<T> {
    override fun set(t: T) = println("Received: $t")
}
fun main() {
    val sink: WriteOnly<String> = Sink<Any>()
    sink.set("hello")
}

Ejemplos de la biblioteca estándar de Kotlin

Comparable<in T> es contravariante: un Comparable<Number> puede comparar cualquier subtipo de Number. List<out T> es covariante: una lista de gatos es una lista de animales.

fun sortNumbers(list: List<Number>) = list.sortedWith(compareBy { it.toDouble() })
fun main() {
    val ints: List<Int> = listOf(3, 1, 2)
    val sorted = sortNumbers(ints)  // OK: List<Int> is List<Number>
    println(sorted)
}

Invarianza: MutableList

MutableList<T> es invariante: puede producir y consumir T, por lo que no existe una relación de subtipo entre distintos tipos T.

fun addNumber(list: MutableList<Number>) { list.add(1.5) }
fun main() {
    val ints = mutableListOf<Int>(1, 2)
    // addNumber(ints) // compile error: MutableList<Int> is not MutableList<Number>
    val nums = mutableListOf<Number>(1, 2)
    addNumber(nums)   // OK
    println(nums)
}

La varianza en el sitio de uso como alternativa

Cuando no puede cambiar la clase, use la varianza en el sitio de uso: out en el sitio de llamada para proyectar un tipo como covariante.

fun copy(from: MutableList<out Any>, to: MutableList<Any>) {
    for (item in from) to.add(item)
}
fun main() {
    val src = mutableListOf("a", "b", "c")
    val dest = mutableListOf<Any>()
    copy(src, dest)
    println(dest)
}

Práctica: patrón Repository

Use la covarianza para repositorios de solo lectura y la contravarianza para destinos de solo escritura, a fin de modelar un flujo de datos claro.

interface Repository<out T> {
    fun findAll(): List<T>
    fun findById(id: Int): T?
}
interface Writer<in T> {
    fun save(entity: T)
}
interface ReadWriteRepo<T> : Repository<T>, Writer<T>
// ReadWriteRepo<User> is neither sub nor super of ReadWriteRepo<Admin>

Guía para decidir la varianza

Elija la varianza según cómo se use T: out = productor (solo devuelve T), in = consumidor (solo acepta T), invariante = ambas operaciones (MutableList, Channel).

// Quick mental model:
// out T: source/producer  — List, Flow, Sequence
// in  T: sink/consumer    — Comparable, Continuation
// invariant: read+write   — MutableList, Channel, MutableStateFlow
fun main() {
    val nums: List<Number> = listOf(1, 2, 3)  // List is out
    println(nums)
}

Clase invariante con función out

Incluso en una clase invariante puede usar out en el sitio de uso para parámetros de función específicos.

class Stack<T>(private val items: MutableList<T> = mutableListOf()) {
    fun push(item: T) = items.add(item)
    fun pop(): T? = items.removeLastOrNull()
}
fun printAll(stack: Stack<out Any>) {
    // can only read, not write
    println(stack.pop())
}
fun main() {
    val s = Stack<String>()
    s.push("hello")
    printAll(s)
}

Comprobación rápida

¿Qué significa out T en un parámetro de tipo?

Resumen

out T (covarianza) permite la relación de subtipo al producir valores. in T (contravarianza) permite la relación de subtipo al consumir valores. Los tipos invariantes (que leen y escriben) no tienen una relación de subtipo entre distintos tipos T.

Preguntas frecuentes

¿La lección «Varianza en el sitio de declaración: in y out» es gratis?

Sí — el texto completo de «Varianza en el sitio de declaración: in y out» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Kotlin Academy, actualiza a CoddyKit PRO. El curso de Kotlin Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Varianza en el sitio de declaración: in y out»?

Use correctamente parámetros de tipo covariantes (out) y contravariantes (in). Practicas Kotlin Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Kotlin Academy?

No se requiere experiencia previa. Kotlin Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.

¿Cuánto tiempo toma la lección «Varianza en el sitio de declaración: in y out»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Kotlin Academy?

Sí. Cada lección de Kotlin Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Funciones genéricas y restricciones de tipos con where
  2. Varianza en el sitio de declaración: in y out
  3. Proyección estrella y cuándo usar *
  4. Borrado de tipos y parámetros de tipo reified
← Volver a Kotlin Academy