out și in (varianță) în Kotlin

Generics Kotlin 1.0

out și in spun cum se moștenesc tipurile generice: out T doar produce valori (covariant), in T doar le consumă (contravariant).

Sintaxă
interface Sursa<out T> { fun urmatorul(): T }        // doar produce T
interface Destinatie<in T> { fun primeste(x: T) }   // doar consumă T

fun copiaza(din: Array<out Any>, in: Array<in Any>)  // varianță la folosire
List<*>                                              // proiecție stea

Ce face out și in (varianță)

Chiar dacă Int este un Number, o MutableList<Int> nu este o MutableList<Number>: altfel ai putea adăuga un Double într-o listă de întregi. Spunem că tipul generic este invariant. Varianța permite relaxarea regulii când este sigur.

out T (covarianță): clasa doar dă valori de tip T (le returnează), nu le primește ca parametru. Atunci Sursa<Int> poate fi folosită ca Sursa<Number>. List<out E> din Kotlin este așa, de aceea un List<Int> merge unde se cere List<Number>.

in T (contravarianță): clasa doar primește valori de tip T. Atunci un Comparator<Number> poate fi folosit ca Comparator<Int>: ce compară orice număr compară și întregi. Varianța poate fi scrisă și la locul folosirii (Array<out Any>), iar List<*> (proiecția stea) înseamnă „o listă de un tip necunoscut”, din care poți doar citi Any?.

Exemple

Exemplul 1: List este covariantă (out)

totalPret cere List<Produs>, dar primește și List<Carte>, pentru că List are out E.

open class Produs(val nume: String, val pret: Int)
class Carte(nume: String, pret: Int, val autor: String) : Produs(nume, pret)

fun totalPret(produse: List<Produs>) = produse.sumOf { it.pret }

fun main() {
    val carti: List<Carte> = listOf(
        Carte("Ion", 35, "Rebreanu"),
        Carte("Amintiri din copilărie", 25, "Creangă"),
    )
    println("Total cărți: ${totalPret(carti)} lei")
    println("Autori: ${carti.map { it.autor }}")
}

Rezultat

Total cărți: 60 lei
Autori: [Rebreanu, Creangă]

Exemplul 2: Interfețe proprii cu out și in

Generator<out T> doar produce valori, iar Afisor<in T> doar le primește. De aceea atribuirile marcate în comentarii compilează.

interface Generator<out T> { fun urmatorul(): T }
interface Afisor<in T> { fun afiseaza(x: T) }

class Numarator : Generator<Int> {
    private var n = 0
    override fun urmatorul(): Int = ++n
}

class AfisorNumere : Afisor<Number> {
    override fun afiseaza(x: Number) = println("Număr: $x")
}

fun main() {
    val gen: Generator<Number> = Numarator()   // Generator<Int> folosit ca Generator<Number> (out)
    val afis: Afisor<Int> = AfisorNumere()     // Afisor<Number> folosit ca Afisor<Int> (in)
    repeat(3) { println(gen.urmatorul()) }
    afis.afiseaza(2026)
}

Rezultat

1
2
3
Număr: 2026

Exemplul 3: Varianță la locul folosirii și proiecția stea

Array este invariant, dar parametrii out Any și in String permit copierea din Array<String> în Array<Any>. List<*> acceptă liste de orice tip.

fun copiaza(din: Array<out String>, spre: Array<in String>) {
    for (i in din.indices) spre[i] = din[i]
}

fun descrie(lista: List<*>) = "Listă cu ${lista.size} elemente, primul: ${lista.firstOrNull()}"

fun main() {
    val orase = arrayOf("Iași", "Sibiu")
    val oriCe: Array<Any> = arrayOf(0, 0)
    copiaza(orase, oriCe)
    println(oriCe.joinToString())

    println(descrie(listOf(1, 2, 3)))
    println(descrie(listOf("Ana", "Ion")))
}

Rezultat

Iași, Sibiu
Listă cu 3 elemente, primul: 1
Listă cu 2 elemente, primul: Ana

Greșeli frecvente

  • Folosirea lui T ca parametru într-o clasă declarată out T: eroare type parameter 'T' is declared as 'out' but occurs in 'in' position.
  • Așteptarea ca MutableList<Int> să meargă unde se cere MutableList<Number>: listele modificabile sunt invariante. Cere List<Number> dacă doar citești.
  • Adăugarea în MutableList<*>: nu este permisă, pentru că tipul elementelor este necunoscut.

Comenzi înrudite

  • Generics (<T>) și where: Generics permit clase și funcții care lucrează cu orice tip (T), verificat la compilare; where adaugă mai multe restricții pe T.
  • in și !in: in verifică dacă o valoare aparține unui interval, unei colecții sau unui text; tot in se folosește în for.
  • interface: interface descrie ce poate face un obiect (metode și proprietăți), fără stare; o clasă poate implementa mai multe interfețe.
  • List: Tipul listei doar pentru citire: elemente în ordine, accesate după poziție, fără funcții de adăugare sau ștergere.