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
Tca parametru într-o clasă declaratăout T: eroaretype parameter 'T' is declared as 'out' but occurs in 'in' position. - Așteptarea ca
MutableList<Int>să meargă unde se cereMutableList<Number>: listele modificabile sunt invariante. CereList<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.