Funcții de extensie în Kotlin

Funcții și lambda Kotlin 1.0

O funcție de extensie adaugă o metodă nouă unui tip existent (String, Int, List), fără să modifici clasa.

Sintaxă
fun TipReceptor.numeFunctie(param: Tip): Rezultat {
    // this = obiectul pe care s-a apelat
}

val TipReceptor.proprietate: Tip get() = ...

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 (sau internal, î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 doar get().

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