Ce face Generics (<T>) și where
Un tip generic are un parametru de tip, scris între < >: List<String>, Map<String, Int>. Și tu poți scrie clase (class Cutie<T>) și funcții (fun <T> primul(lista: List<T>)) generice. Compilatorul verifică tipurile: într-o Cutie<Int> nu poți pune un text, iar când scoți valoarea nu ai nevoie de conversie. De obicei tipul se deduce singur: Cutie(5) este Cutie<Int>.
O limită superioară restrânge tipurile acceptate: <T : Number> acceptă doar numere, deci în corp poți folosi toDouble(). Fără limită, T poate fi orice, inclusiv nullable (limita implicită este Any?); <T : Any> exclude null.
Când T trebuie să respecte mai multe condiții, le scrii cu where după semnătură: where T : CharSequence, T : Comparable<T>. Tipurile generice se șterg la rulare pe JVM; pentru is T ai nevoie de reified.
Exemple
Exemplul 1: Clasă și funcție generică
Cutie<T> păstrează o valoare de orice tip. ultimul merge pentru liste de orice tip și returnează T?.
class Cutie<T>(val continut: T) {
fun descrie() = "Cutie cu: $continut"
}
fun <T> ultimul(lista: List<T>): T? = if (lista.isEmpty()) null else lista[lista.size - 1]
fun main() {
val cutieText = Cutie("mere")
val cutieNumar = Cutie(42)
println(cutieText.descrie())
println(cutieNumar.continut + 8)
println(ultimul(listOf("Ana", "Mihai", "Ioana")))
println(ultimul(listOf(3.5, 7.25)))
println(ultimul(emptyList<Int>()))
}
Rezultat
Cutie cu: mere
50
Ioana
7.25
null
Exemplul 2: Limită superioară: și >
medie acceptă orice listă de numere. celMaiMare are nevoie ca elementele să poată fi comparate.
fun <T : Number> medie(valori: List<T>): Double = valori.sumOf { it.toDouble() } / valori.size
fun <T : Comparable<T>> celMaiMare(a: T, b: T): T = if (a >= b) a else b
fun main() {
println(medie(listOf(9, 10, 8)))
println(medie(listOf(2.5, 3.5)))
println(celMaiMare(7, 12))
println(celMaiMare("Iași", "Cluj"))
}
Rezultat
9.0
3.0
12
Iași
Exemplul 3: Mai multe restricții cu where
T trebuie să fie text (CharSequence) și comparabil. String le respectă pe amândouă.
fun <T> celMaiLungApoiAlfabetic(valori: List<T>): T?
where T : CharSequence, T : Comparable<T> =
valori.sortedWith(compareByDescending<T> { it.length }.thenBy { it }).firstOrNull()
fun main() {
println(celMaiLungApoiAlfabetic(listOf("Arad", "Iași", "Brăila", "Galați")))
println(celMaiLungApoiAlfabetic(listOf("Cluj", "Alba")))
println(celMaiLungApoiAlfabetic(emptyList<String>()))
}
Rezultat
Brăila
Alba
null
Greșeli frecvente
x is Tîntr-o funcție generică obișnuită: eroarecannot check for instance of erased type 'T'. Foloseșteinline+reified.- Presupunerea că
Tnu poate fi null: fără limită,Tpoate fiString?. Scrie<T : Any>dacă vrei să excluzinull. - Așteptarea ca
List<Int>să poată fi folosită caMutableList<Number>: vezi varianța (out/in).
Comenzi înrudite
out și in (varianță): out și in spun cum se moștenesc tipurile generice: out T doar produce valori (covariant), in T doar le consumă (contravariant).reified: reified păstrează tipul generic T la rulare într-o funcție inline, ca să poți scrie x is T sau T::class.typealias: Dă un nume nou, mai scurt sau mai clar, unui tip existent; nu creează un tip nou.vararg: vararg permite unei funcții să primească oricâte argumente de același tip, văzute în corp ca un tablou.class: class declară o clasă: un tip nou care grupează date (proprietăți) și comportament (metode).