operator în Kotlin

Funcții și lambda Kotlin 1.0

operator marchează o funcție cu nume special (plus, get, contains, compareTo...) ca să poată fi folosită prin +, [], in sau <.

Sintaxă
operator fun plus(alt: Tip): Tip        // a + b
operator fun get(index: Int): Tip       // a[i]
operator fun contains(x: Tip): Boolean  // x in a
operator fun invoke(): Tip              // a()

Ce face operator

În Kotlin operatorii sunt de fapt apeluri de funcții cu nume fixe. a + b înseamnă a.plus(b), a[i] înseamnă a.get(i), x in a înseamnă a.contains(x), a < b înseamnă a.compareTo(b) < 0. Ca operatorul să meargă pe clasa ta, scrii funcția cu numele respectiv și o marchezi cu operator.

Nume des folosite: plus, minus, times, div, rem (aritmetici), unaryMinus, inc, dec, get/set (indexare), contains (in), rangeTo (..), compareTo, invoke (apel ca funcție), componentN (destructurare), getValue/setValue (proprietăți delegate). == folosește equals, care nu are nevoie de operator (se suprascrie cu override).

+= folosește plusAssign dacă există, altfel a = a + b (pentru var). Operatorii pot fi scriși și ca extensii, deci poți adăuga + unei clase pe care nu o poți modifica.

Exemple

Exemplul 1: Bani: plus, times și compareTo

Clasa Bani păstrează suma în bani (întregi). Datorită funcțiilor operator, sumele se adună, se înmulțesc și se compară cu operatorii obișnuiți.

data class Bani(val bani: Long) : Comparable<Bani> {
    operator fun plus(alt: Bani) = Bani(bani + alt.bani)
    operator fun times(n: Int) = Bani(bani * n)
    override operator fun compareTo(other: Bani) = bani.compareTo(other.bani)
    override fun toString() = "${bani / 100},${(bani % 100).toString().padStart(2, '0')} lei"
}

fun main() {
    val paine = Bani(450)
    val lapte = Bani(799)
    var total = paine * 2 + lapte
    println("Total: $total")
    total += Bani(1001)
    println("Cu ouă: $total")
    println("Pâinea e mai ieftină decât laptele? ${paine < lapte}")
}

Rezultat

Total: 16,99 lei
Cu ouă: 27,00 lei
Pâinea e mai ieftină decât laptele? true

Exemplul 2: Indexare cu get/set și operatorul in

catalog["Ana"] apelează get, atribuirea apelează set, iar "Ana" in catalog apelează contains.

class Catalog {
    private val note = mutableMapOf<String, Int>()
    operator fun get(elev: String): Int = note[elev] ?: 0
    operator fun set(elev: String, nota: Int) { note[elev] = nota }
    operator fun contains(elev: String) = elev in note
}

fun main() {
    val catalog = Catalog()
    catalog["Ana"] = 10
    catalog["Mihai"] = 7
    println("Ana: ${catalog["Ana"]}")
    println("Ioana: ${catalog["Ioana"]}")
    println("Mihai este în catalog? ${"Mihai" in catalog}")
    println("Ioana lipsește din catalog? ${"Ioana" !in catalog}")
}

Rezultat

Ana: 10
Ioana: 0
Mihai este în catalog? true
Ioana lipsește din catalog? true

Exemplul 3: invoke și operator ca extensie

Un obiect cu invoke poate fi apelat ca o funcție. unaryMinus este adăugat ca extensie pe Pair, o clasă din biblioteca standard.

class Salut(private val formula: String) {
    operator fun invoke(nume: String) = "$formula, $nume!"
}

operator fun Pair<Int, Int>.unaryMinus() = Pair(-first, -second)

fun main() {
    val buna = Salut("Bună dimineața")
    println(buna("Elena"))
    println(buna("Radu"))

    val punct = 3 to -5
    println(-punct)
}

Rezultat

Bună dimineața, Elena!
Bună dimineața, Radu!
(-3, 5)

Greșeli frecvente

  • Uitarea cuvântului operator: funcția plus există, dar a + b dă eroarea 'operator' modifier is required on 'fun plus(alt: Bani): Bani'.
  • Nume greșit: operatorii au nume fixe (times, nu multiply; div, nu divide).
  • Operatori cu sens surprinzător (de exemplu + care șterge ceva): păstrează sensul matematic obișnuit, altfel codul înșală cititorul.

Comenzi înrudite

  • infix: infix permite apelarea unei funcții cu un parametru fără punct și paranteze: a to b în loc de a.to(b).
  • Destructuring (val (a, b) = ...): Destructuring desface un obiect în mai multe variabile deodată: val (nume, nota) = pereche.
  • 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.
  • Proprietăți delegate (by lazy, observable, map): O proprietate delegată (val x by …) lasă un alt obiect să decidă ce se întâmplă la citire și scriere: lazy, observable, map.
  • data class: data class este o clasă pentru date: primește automat toString, equals, hashCode, copy și componentN.