0Pricing
Kotlin Academy · Lezione

Varianza dichiarata nel punto di dichiarazione: in e out

Utilizzi correttamente i parametri di tipo covarianti (out) e controvarianti (in).

Varianza dichiarata nel punto di dichiarazione: in e out è una lezione Kotlin Academy gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Kotlin Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Kotlin Academy include 4 lezioni in totale.

Il problema della varianza

In Kotlin, List<String> è un sottotipo di List<Any> perché List è dichiarata con out T. Senza annotazioni di varianza, questa relazione non sarebbe valida.

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 che la classe può soltanto produrre valori T, cioè restituirli, e non consumarli. Questo rende Producer<Dog> un sottotipo di 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>
}

Controvarianza con in

in T significa che la classe può soltanto consumare valori T, cioè accettarli come parametri, e non produrli. Questo rende Consumer<Animal> un sottotipo di 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>
}

Vincolo di out: nessuna posizione in

Con out T, T non può comparire nella posizione dei parametri (in). Il compilatore fa rispettare questa regola.

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())
}

Vincolo di in: nessuna posizione out

Con in T, T non può comparire nella posizione del valore restituito (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")
}

Esempi dalla libreria standard Kotlin

Comparable<in T> è controvariante: un Comparable<Number> può confrontare qualsiasi sottotipo di Number. List<out T> è covariante: una lista di gatti è una lista di animali.

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> è invariante: può sia produrre sia consumare T, quindi non esiste una relazione tra sottotipi di tipi T diversi.

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)
}

Varianza use-site come alternativa

Quando non può modificare la classe, utilizzi la varianza use-site: out nel punto di chiamata per proiettare un tipo in modo 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)
}

Pratica: pattern del repository

Utilizzi la covarianza per repository di sola lettura e la controvarianza per sink di sola scrittura, così da modellare un flusso di dati chiaro.

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>

Guida alla scelta della varianza

Scelga la varianza in base all'uso di T: out = produttore (restituisce soltanto T), in = consumatore (accetta soltanto T), invariante = entrambi (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)
}

Classe invariante con funzione out

Anche in una classe invariante è possibile utilizzare out a livello di use-site per specifici parametri di funzione.

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)
}

Verifica rapida

Cosa significa out T su un parametro di tipo?

Riepilogo

out T (covarianza) consente il sottotipaggio quando si producono valori. in T (controvarianza) consente il sottotipaggio quando si consumano valori. I tipi invarianti, che leggono e scrivono, non hanno una relazione tra sottotipi T diversi.

Domande Frequenti

La lezione «Varianza dichiarata nel punto di dichiarazione: in e out» è gratuita?

Sì — il testo completo di «Varianza dichiarata nel punto di dichiarazione: in e out» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Kotlin Academy, passa a CoddyKit PRO. Il corso Kotlin Academy include 4 lezioni in totale.

Cosa imparerò in «Varianza dichiarata nel punto di dichiarazione: in e out»?

Utilizzi correttamente i parametri di tipo covarianti (out) e controvarianti (in). Eserciti Kotlin Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Kotlin Academy?

Non è richiesta alcuna esperienza precedente. Kotlin Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.

Quanto tempo richiede la lezione «Varianza dichiarata nel punto di dichiarazione: in e out»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Kotlin Academy?

Sì. Ogni lezione Kotlin Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Funzioni generiche e vincoli di tipo con where
  2. Varianza dichiarata nel punto di dichiarazione: in e out
  3. Star projection e quando usare *
  4. Type erasure e parametri di tipo reified
← Torna a Kotlin Academy