Ce face Funcții de extensie
Cu o funcție de extensie poți scrie "Ana Pop".initiale() deși clasa String nu are metoda initiale. O declari cu numele tipului înainte de numele funcției: fun String.initiale(): String. În corp, this (sau direct numele metodelor) se referă la obiectul pe care ai apelat funcția, numit receptor.
Extensiile nu modifică clasa și nu văd membrii ei private. Compilatorul le transformă în funcții obișnuite care primesc receptorul ca prim parametru. Ele se aleg după tipul declarat al variabilei, nu după tipul real al obiectului, iar dacă clasa are deja o metodă cu același nume, metoda clasei câștigă.
Poți declara și proprietăți de extensie (fără stare, doar cu get()) și extensii pe tipuri nullable (fun String?.sauGol()), care pot fi apelate și pe null. Biblioteca standard este construită în mare parte din extensii: filter, let, uppercase sunt extensii.
Exemple
Exemplul 1: Extensii pentru String și Double
initiale folosește split pe receptor. lei formatează orice Double ca sumă de bani.
fun String.initiale(): String =
split(" ").filter { it.isNotEmpty() }.joinToString("") { it.first().uppercase() }
fun Double.lei(): String = "%.2f lei".format(this)
fun main() {
println("Ana Maria Pop".initiale())
println("ion creangă".initiale())
println(1234.5.lei())
println((19.99 * 3).lei())
}
Rezultat
AMP
IC
1234.50 lei
59.97 lei
Exemplul 2: Proprietate de extensie și extensie pe tip nullable
medie se citește ca o proprietate a listei. sauAnonim poate fi apelată și când valoarea este null.
val List<Int>.medie: Double
get() = if (isEmpty()) 0.0 else sum().toDouble() / size
fun String?.sauAnonim(): String = if (this.isNullOrBlank()) "anonim" else this
fun main() {
val note = listOf(10, 9, 7, 8)
println("Media: ${note.medie}")
println("Listă goală: ${emptyList<Int>().medie}")
val nume: String? = null
println("Autor: ${nume.sauAnonim()}")
println("Autor: ${"Elena".sauAnonim()}")
}
Rezultat
Media: 8.5
Listă goală: 0.0
Autor: anonim
Autor: Elena
Exemplul 3: Metoda clasei câștigă în fața extensiei
Clasa Produs are deja descriere(), deci extensia cu același nume este ignorată. Extensia etichetă nouă funcționează normal.
class Produs(val nume: String, val pret: Int) {
fun descriere() = "metoda clasei: $nume"
}
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
fun Produs.descriere() = "extensie: $nume"
fun Produs.eticheta() = "$nume - $pret lei"
fun main() {
val caiet = Produs("Caiet", 7)
println(caiet.descriere())
println(caiet.eticheta())
}
Rezultat
metoda clasei: Caiet
Caiet - 7 lei
Greșeli frecvente
- Așteptarea ca o extensie să acceseze câmpuri
private: extensiile văd doar ce este public (sauinternal, în același modul). - Declararea unei extensii cu același nume ca o metodă existentă: metoda clasei se apelează mereu, iar compilatorul avertizează (
this extension is shadowed by a member). - Proprietăți de extensie cu valoare inițială (
val String.x = 5): nu au unde păstra date, deci pot avea doarget().
Comenzi înrudite
fun: fun declară o funcție: un bloc de cod cu nume, care primește parametri și poate returna un rezultat.lambda: O lambda este o funcție fără nume, scrisă între acolade, pe care o poți pune într-o variabilă sau trimite altei funcții.this: this înseamnă obiectul curent: obiectul pe care s-a apelat metoda sau receptorul unei funcții de extensie.null și null safety (?. ?: !!): null înseamnă „nicio valoare”. Kotlin separă tipurile care acceptă null (String?) și oferă ?., ?: și !! pentru ele.infix: infix permite apelarea unei funcții cu un parametru fără punct și paranteze: a to b în loc de a.to(b).