Preopterećenje operatera u Kotlinu

1. Pregled

U ovom uputstvu govorit ćemo o konvencijama koje Kotlin pruža za podršku preopterećenju operatora.

2. The operater Ključna riječ

U Javi su operateri vezani uz određene tipove Java. Na primjer, Niz i numerički tipovi u Javi mogu koristiti operator + za spajanje, odnosno dodavanje. Nijedna druga vrsta Java ne može ponovno koristiti ovaj operater u svoju korist. Kotlin, naprotiv, pruža niz konvencija koje podržavaju ograničene Preopterećenje operatera.

Počnimo s jednostavnim klasa podataka:

podatkovna klasa Točka (val x: Int, val y: Int)

Ovu ćemo klasu podataka poboljšati s nekoliko operatora.

Da bi funkciju Kotlin s unaprijed definiranim imenom pretvorio u operator, trebali bismo funkciju označiti znakom operater modifikator. Na primjer, možemo preopteretiti “+” operater:

zabava operatora Point.plus (ostalo: Point) = Point (x + other.x, y + other.y)

Na ovaj način možemo dodati dva Bodovi s “+”:

>> val p1 = Točka (0, 1) >> val p2 = Točka (1, 2) >> println (p1 + p2) Točka (x = 1, y = 3)

3. Preopterećenje za uske operacije

Unarne operacije su one koje rade na samo jednom operandu. Na primjer, -a, ++ ili ! a su uske operacije. Općenito, funkcije koje će preopteretiti unarne operatore ne uzimaju parametre.

3.1. Unary Plus

Što kažete na konstrukciju a Oblik neke vrste s nekolicinom Bodovi:

val s = oblik {+ Točka (0, 0) + Točka (1, 1) + Točka (2, 2) + Točka (3, 4)}

U Kotlinu je to savršeno moguće s unaryPlus funkcija operatora.

Budući da je a Oblik je samo zbirka Bodovi, tada možemo napisati razred, zamotajući nekoliko Točkas mogućnošću dodavanja više:

class Shape {private val points = mutableListOf () operator zabave Point.unaryPlus () {points.add (this)}}

I imajte na umu da je ono što nam je dalo oblik {…} sintaksa je trebala koristiti a Lambda s Prijemnici:

zabavan oblik (init: Shape. () -> Unit): Shape {val shape = Shape () shape.init () return shape}

3.2. Unary Minus

Pretpostavimo da imamo a Točka imenovan "P" i negirat ćemo njegove koordinacije koristeći nešto poput "-P". Zatim, sve što moramo učiniti je definirati operatersku funkciju imenovanu unaryMinus na Točka:

zabava operatora Point.unaryMinus () = Point (-x, -y)

Zatim, svaki put kad dodamo a “-“ prefiks prije instance Točka, prevodilac prevodi u a unaryMinus poziv funkcije:

>> val p = Točka (4, 2) >> println (-p) Točka (x = -4, y = -2)

3.3. Prirast

Svaku koordinatu možemo povećati za jednu samo implementirajući funkciju operatora imenovanu uklj:

zabava operatora Point.inc () = Point (x + 1, y + 1)

Postfix “++” operator, prvo vraća trenutnu vrijednost, a zatim povećava vrijednost za jedan:

>> var p = Točka (4, 2) >> println (p ++) >> println (p) Točka (x = 4, y = 2) Točka (x = 5, y = 3)

Naprotiv, prefiks “++” operater, prvo povećava vrijednost, a zatim vraća novo uvećanu vrijednost:

>> println (++ p) Točka (x = 6, y = 4)

Također, od “++” operator ponovno dodjeljuje primijenjenu varijablu koju ne možemo koristiti val sa njima.

3.4. Dekrement

Sasvim slično povećanju, svaku koordinatu možemo dekrementirati primjenom dec funkcija operatora:

zabava operatora Point.dec () = Točka (x - 1, y - 1)

dec također podržava poznatu semantiku za operatore prije i nakon smanjenja kao i za redovite numeričke tipove:

>> var p = Točka (4, 2) >> println (p--) >> println (p) >> println (- p) Točka (x = 4, y = 2) Točka (x = 3, y = 1) Točka (x = 2, y = 0)

Isto kao ++ ne možemo koristiti s vals.

3.5. Ne

Kako bi bilo samo okretati koordinate ! str? To možemo učiniti s ne:

zabava operatora Point.not () = Point (y, x)

Jednostavno rečeno, prevodilac prevodi bilo koji "! P" na poziv funkcije na "ne" unarna funkcija operatora:

>> val p = Točka (4, 2) >> println (! p) Točka (x = 2, y = 4)

4. Preopterećenje za binarne operacije

Binarni operatori, kao što im samo ime govori, su oni koji rade na dva operanda. Dakle, funkcije koje preopterećuju binarne operatore trebaju prihvatiti barem jedan argument.

Krenimo od aritmetičkih operatora.

4.1. Plus aritmetički operator

Kao što smo vidjeli ranije, u Kotlinu možemo preopteretiti osnovne matematičke operatore. Možemo koristiti “+” zbrojiti dva Bodovi zajedno:

zabava operatora Point.plus (ostalo: Point): Point = Point (x + other.x, y + other.y)

Tada možemo napisati:

>> val p1 = točka (1, 2) >> val p2 = točka (2, 3) >> println (p1 + p2) točka (x = 3, y = 5)

Od plus je binarna funkcija operatora, trebali bismo proglasiti parametar za funkciju.

Sada je većina nas iskusila neelegantnost zbrajanja dvoje BigIntegers:

BigInteger nula = BigInteger.ZERO; BigInteger one = BigInteger.ONE; jedan = jedan.dodaj (nula);

Ispostavilo se da postoji bolji način za dodavanje dva BigIntegers u Kotlinu:

>> val jedan = BigInteger.ONE println (jedan + jedan)

Ovo djeluje jer Kotlin standardna knjižnica sama dodaje svoj pošten udio operatora proširenja na ugrađenim vrstama poput BigInteger.

4.2. Ostali aritmetički operatori

Slično plus, oduzimanje, množenje, podjela, i Podsjetnik rade na isti način:

zabava operatora Point.minus (ostalo: Point): Point = Point (x - other.x, y - other.y) operator zabave Point.times (other: Point): Point = Point (x * other.x, y * ostalo.y) zabava operatora Point.div (ostalo: Point): Point = Point (x / other.x, y / other.y) zabava operatora Point.rem (ostalo: Point): Point = Point (x% ostalo. x, y% ostalo.y)

Zatim, kompajler Kotlin prevodi bilo koji poziv na “-“, “*”, "/", ili "%" do "minus", "Puta", "Div" ili "rem" , odnosno:

>> val p1 = Točka (2, 4) >> val p2 = Točka (1, 4) >> println (p1 - p2) >> println (p1 * p2) >> println (p1 / p2) Točka (x = 1, y = 0) Točka (x = 2, y = 16) Točka (x = 2, y = 1)

Ili, što kažete na skaliranje a Točka numeričkim faktorom:

zabava operatora Point.times (faktor: Int): Point = Point (x * faktor, y * faktor)

Na ovaj način možemo napisati nešto poput “P1 * 2”:

>> val p1 = Točka (1, 2) >> println (p1 * 2) Točka (x = 2, y = 4)

Kao što možemo primijetiti iz prethodnog primjera, ne postoji obveza da dva operanda budu istog tipa. Isto vrijedi i za vrste povratka.

4.3. Komutativnost

Preopterećeni operatori nisu uvijek komutativni. To je, ne možemo zamijeniti operande i očekivati ​​da će stvari funkcionirati što je moguće glatko.

Na primjer, možemo skalirati a Točka integralnim faktorom množenjem na an Int, recimo “P1 * 2”, ali ne i obrnuto.

Dobra vijest je da možemo definirati funkcije operatora na ugrađenim vrstama Kotlin ili Java. Da bi se "2 * p1" rad, možemo definirati operatora na Int:

zabava operatora Int.vrijeme (točka: Točka): Točka = Točka (točka.x * ovo, točka.y * ovo)

Sada možemo rado koristiti "2 * p1" također:

>> val p1 = Točka (1, 2) >> println (2 * p1) Točka (x = 2, y = 4)

4.4. Složeni zadaci

Sad kad možemo dodati dva BigIntegers s “+” operatora, možda ćemo moći koristiti složeni zadatak za “+” koji je “+=”. Pokušajmo s ovom idejom:

var one = BigInteger.ONE one + = one

Recimo, prema zadanim postavkama, kada implementiramo jedan od aritmetičkih operatora "plus", Kotlin ne podržava samo poznato “+” operater, također čini istu stvar za odgovarajuće složeni zadatak, što je "+ =".

To znači da bez ikakvog posla možemo i:

var point = Point (0, 0) point + = Point (2, 2) point - = Point (1, 1) point * = Point (2, 2) point / = Point (1, 1) point / = Point ( 2, 2) točka * = 2

Ali ponekad ovo zadano ponašanje nije ono što tražimo. Pretpostavimo da ćemo koristiti “+=” za dodavanje elementa u Promjenjiva kolekcija.

Za ove scenarije možemo biti eksplicitni u vezi s implementacijom operacijske funkcije imenovane plusDodijeli:

operator zabave MutableCollection.plusAssign (element: T) {dodaj (element)}

Za svaki aritmetički operator postoji odgovarajući operator složenog dodjeljivanja koji svi imaju "Dodijeliti" sufiks. Odnosno postoje plusAssign, minusAssign, timesAssign, divAssign, i remAssign:

>> val boje = mutableListOf ("crvena", "plava") >> boje + = "zelena" >> println (boje) [crvena, plava, zelena]

Sve funkcije složenog operatora dodjele moraju se vratiti Jedinica.

4.5. Konvencija o jednakim

Ako poništimo jednako metodu, tada možemo koristiti “==” i “!=” operatora, isto:

class Money (val iznos: BigDecimal, val valuta: Valuta): Usporediv {// izostavljeno nadjačavanje zabave jednako (ostalo: Bilo koji?): Logičko {if (ovo === ostalo) return true ako (drugo! je novac) return false if (iznos! = other.amount) return false if (currency! = other.currency) return false return true} // Jednaka je kompatibilna implementacija hashcode-a} 

Kotlin prevodi svaki poziv na “==” i “!=” operateri na an jednako poziv funkcije, očito kako bi se “!=” rada, rezultat poziva funkcije se obrće. Imajte na umu da nam u ovom slučaju ne treba operater ključna riječ.

4.6. Operatori usporedbe

Vrijeme je za udaranje BigInteger opet!

Pretpostavimo da ćemo pokrenuti neku logiku ako je uslovno BigInteger je veći od drugog. U Javi rješenje nije baš toliko čisto:

if (BigInteger.ONE.compareTo (BigInteger.ZERO)> 0) {// neka logika}

Kada se koristi isti BigInteger u Kotlinu možemo čarobno napisati ovo:

if (BigInteger.ONE> BigInteger.ZERO) {// ista logika}

Ova čarolija je moguća jer Kotlin ima poseban tretman Jave Usporedive.

Jednostavno rečeno, možemo nazvati usporediTo metoda u Usporedive sučelje po nekoliko Kotlinovih konvencija. Zapravo, sve usporedbe koje je napravio “<“, “”, ili “>=” bi se preveo na a usporediTo poziv funkcije.

Da bismo koristili operatore usporedbe tipa Kotlin, moramo ga implementirati Usporedive sučelje:

klasa Novac (iznos valute: BigDecimal, valuta valute: Valuta): Usporedivo {nadjačati zabavu compareTo (ostalo: Novac): Int = pretvori (Valuta.DOLARI) .compareTo (other.convert (Valuta.DOLARI)) zabava pretvori (valuta: Valuta): BigDecimal = // izostavljen}

Tada novčane vrijednosti možemo usporediti tako jednostavno kao:

val oneDollar = Novac (BigDecimal.ONE, Currency.DOLLARS) val tenDollars = Novac (BigDecimal.TEN, Currency.DOLLARS) if (oneDollar <tenDollars) {// izostavljen}

Budući da je usporediTo funkcija u Usporedive sučelje je već označeno znakom operater modifikator, ne trebamo ga sami dodavati.

4.7. U Konvenciji

Kako bi provjerili pripada li element a Stranica, možemo koristiti "u" konvencija:

operator fun Page.contens (element: T): Boolean = element u elementima ()

Opet, prevodilac bi preveo "u" i "!u" konvencije za poziv funkcije na sadrži funkcija operatora:

>> val stranica = firstPageOfSomething () >> "Ovo" na stranici >> "To"! na stranici

Predmet s lijeve strane "u" bit će proslijeđen kao argument sadrži i sadrži funkcija bi bila pozvana na desnom operandu.

4.8. Nabavite Indexer

Indeksatori omogućuju indeksiranje primjeraka tipa baš kao nizovi ili zbirke. Pretpostavimo da ćemo paginiranu zbirku elemenata modelirati kao Stranica, besramno otkinuvši ideju iz Spring Data:

Stranica sučelja {fun pageNumber (): Int fun pageSize (): Int fun elements (): MutableList}

Obično, da bi se element dohvatio iz a Stranica, prvo bismo trebali nazvati elementi funkcija:

>> val stranica = firstPageOfSomething () >> page.elements () [0]

Budući da je Stranica sam je samo otmjeni omot za drugu kolekciju, a mi možemo koristiti operatore indeksa da poboljšamo njegov API:

operator zabave Page.get (indeks: Int): T = elementi () [indeks]

Kompajler Kotlin zamjenjuje bilo koji stranica [indeks] na a Stranica do a dobiti (indeks) poziv funkcije:

>> val stranica = firstPageOfSomething () >> stranica [0]

Možemo ići još dalje dodavanjem argumenta onoliko koliko želimo dobiti deklaracija metode.

Pretpostavimo da ćemo dohvatiti dio omotane kolekcije:

operator zabave Page.get (start: Int, endExclusive: Int): List = elements (). subList (start, endExclusive)

Tada možemo izrezati a Stranica Kao:

>> val stranica = firstPageOfSomething () >> stranica [0, 3]

Također, možemo koristiti bilo koju vrstu parametara za dobiti funkcija operatora, ne samo Int.

4.9. Postavite Indexer

Pored upotrebe indeksatora za implementaciju get-like semantika, možemo ih koristiti za oponašanje skupovnih operacija, isto. Sve što moramo učiniti je definirati funkciju operatora imenovanu postavljen s najmanje dva argumenta:

operator zabave Page.set (index: Int, value: T) {elements () [index] = value}

Kad izjavimo a postavljen funkcija sa samo dva argumenta, prvi treba upotrijebiti unutar zagrade, a drugi nakon zadatak:

val stranica: Stranica = firstPageOfSomething () stranica [2] = "Nešto novo"

The postavljen funkcija može imati više od samo dva argumenta. Ako je tako, posljednji je parametar vrijednost, a ostatak argumenata treba proslijediti unutar zagrada.

4.10. Prizivati

U Kotlinu i mnogim drugim programskim jezicima moguće je pozvati funkciju pomoću functionName (args) sintaksa. Također je moguće oponašati sintaksu poziva funkcije s prizivati funkcije operatora. Na primjer, kako bi se koristila stranica (0) umjesto stranica [0] za pristup prvom elementu možemo proglasiti proširenje:

zabava operatora Page.invoke (indeks: Int): T = elementi () [indeks]

Tada možemo koristiti sljedeći pristup za dohvaćanje određenog elementa stranice:

assertEquals (stranica (1), "Kotlin")

Ovdje Kotlin prevodi zagrade u poziv na prizivati metoda s odgovarajućim brojem argumenata. Štoviše, možemo proglasiti prizivati operator s bilo kojim brojem argumenata.

4.11. Iteracijska konvencija

Što kažete na ponavljanje a Stranica poput ostalih kolekcija? Moramo samo proglasiti funkciju operatora imenovanu iterator s Iterator kao tip povrata:

operator zabave Page.iterator () = elements (). iterator ()

Tada možemo ponoviti a Stranica:

val page = firstPageOfSomething () for (e na stranici) {// Učinite nešto sa svakim elementom}

4.12. Konvencija o dometu

U Kotlinu, možemo stvoriti raspon pomoću “..” operater. Na primjer, “1..42” stvara raspon s brojevima između 1 i 42.

Ponekad je razumno koristiti operator dometa na drugim ne-numeričkim vrstama. Kotlin standardna knjižnica nudi a dometTo konvencija o svima Usporedive:

operater zabava  T.rangeTo (ono: T): ClosedRange = ComparableRange (ovo, ono)

Ovo možemo koristiti da bismo dobili nekoliko uzastopnih dana kao raspon:

val sada = LocalDate.now () val dana = sada..now.plusDays (42)

Kao i kod ostalih operatora, kompajler Kotlin zamjenjuje bilo koji “..” s dometTo poziv funkcije.

5. Razumno koristite operatore

Preopterećenje operatora snažna je značajka u Kotlinu što nam omogućuje pisanje sažetijih, a ponekad i čitljivijih kodova. Međutim, s velikom moći dolazi i velika odgovornost.

Preopterećenje operatera naš kod može zbuniti ili ga je čak teško pročitati kada se prečesto koristi ili povremeno zloupotrijebi.

Stoga, prije dodavanja novog operatora određenoj vrsti, prvo pitajte je li operator semantički dobar za ono što pokušavamo postići. Ili pitajte možemo li postići isti učinak normalnim i manje čarobnim apstrakcijama.

6. Zaključak

U ovom smo članku saznali više o mehanici preopterećenja operatera u Kotlinu i kako koristi skup konvencija da bi to postigao.

Provedbu svih ovih primjera i isječaka koda možete pronaći u projektu GitHub.


$config[zx-auto] not found$config[zx-overlay] not found