Overbelastning av operatør i Kotlin

1. Oversikt

I denne opplæringen skal vi snakke om konvensjonene som Kotlin gir for å støtte operatørens overbelastning.

2. Den operatør Nøkkelord

I Java er operatører knyttet til spesifikke Java-typer. For eksempel, String og numeriske typer i Java kan bruke + -operatøren for henholdsvis sammenkobling og tillegg. Ingen andre Java-typer kan bruke denne operatøren på nytt for egen fordel. Tvert imot gir Kotlin et sett med konvensjoner som støtter begrenset Overbelastning av operatør.

La oss starte med en enkel dataklasse:

dataklasse Punkt (val x: Int, val y: Int)

Vi skal forbedre denne dataklassen med noen få operatører.

For å gjøre en Kotlin-funksjon med et forhåndsdefinert navn til en operatør, vi bør merke funksjonen med operatør modifikator. For eksempel kan vi overbelaste “+” operatør:

operator moro Point.plus (annet: Punkt) = Punkt (x + annet.x, y + annet.y)

På denne måten kan vi legge til to Poeng med “+”:

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

3. Overbelastning for unariske operasjoner

Unary operasjoner er de som fungerer på bare en operand. For eksempel, -a, a ++ eller !en er unariske operasjoner. Vanligvis tar funksjoner som vil overbelaste unære operatører ingen parametere.

3.1. Unary Plus

Hva med å konstruere en Form av noe slag med noen få Poeng:

val s = form {+ Punkt (0, 0) + Punkt (1, 1) + Punkt (2, 2) + Punkt (3, 4)}

I Kotlin er det fullt mulig med unaryPlus operatørfunksjon.

Siden a Form er bare en samling av Poeng, så kan vi skrive en klasse og pakke inn noen få Punkts med muligheten til å legge til flere:

class Shape {private val poeng = mutableListOf () operator moro Point.unaryPlus () {poeng.add (dette)}}

Og merk at det som ga oss form {…} syntaksen var å bruke en Lambda med Mottakere:

morsom form (init: Form. () -> Enhet): Form {val form = Form () form.init () returform}

3.2. Unary minus

Anta at vi har en Punkt heter “P” og vi vil negere koordineringene ved å bruke noe lignende “-P”. Så er alt vi trenger å gjøre å definere en operatørfunksjon som heter unaryMinus Punkt:

operator moro Point.unaryMinus () = Punkt (-x, -y)

Hver gang vi legger til en “-“ prefikset før en forekomst av Punktoversetter kompilatoren det til en unaryMinus funksjonsanrop:

>> val p = Punkt (4, 2) >> println (-p) Punkt (x = -4, y = -2)

3.3. Inkrement

Vi kan øke hver koordinat med en bare ved å implementere en operatørfunksjon som heter inkl. mva:

operator moro Point.inc () = Point (x + 1, y + 1)

Postfix “++” operatør, returnerer den nåværende verdien og øker deretter verdien med en:

>> var p = Punkt (4, 2) >> println (p ++) >> println (p) Punkt (x = 4, y = 2) Punkt (x = 5, y = 3)

Tvert imot prefikset “++” operatør, øker først verdien og returnerer deretter den nylig økte verdien:

>> println (++ p) Punkt (x = 6, y = 4)

Også, siden “++” operatøren tilordner den anvendte variabelen på nytt, vi kan ikke bruke val med dem.

3.4. Nedgang

Ganske lik trinn, kan vi redusere hver koordinat ved å implementere des operatørfunksjon:

operator moro Point.dec () = Punkt (x - 1, y - 1)

des støtter også den kjente semantikken for før- og etterdekrimeringsoperatører som for vanlige numeriske typer:

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

Også som ++ vi kan ikke bruke med vals.

3.5. Ikke

Hva med å bla koordinatene bare forbi ! s? Vi kan gjøre dette med ikke:

operator moro Point.not () = Point (y, x)

Enkelt sagt, oversetter kompilatoren hvilken som helst “! P” til en funksjonsanrop til "ikke" unary operatørfunksjon:

>> val p = Punkt (4, 2) >> println (! p) Punkt (x = 2, y = 4)

4. Overbelastning for binær drift

Binære operatører, som navnet antyder, er de som fungerer på to operander. Så funksjoner som overbelaster binære operatører, bør akseptere minst ett argument.

La oss starte med regneoperatørene.

4.1. Pluss aritmetisk operatør

Som vi så tidligere, kan vi overbelaste grunnleggende matematiske operatører i Kotlin. Vi kan bruke “+” for å legge til to Poeng sammen:

operator moro Point.plus (annet: Punkt): Punkt = Punkt (x + annet.x, y + annet.y)

Så kan vi skrive:

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

Siden i tillegg til er en binær operatørfunksjon, bør vi erklære en parameter for funksjonen.

Nå har de fleste av oss opplevd inelegansen ved å legge sammen to BigIntegers:

BigInteger zero = BigInteger.ZERO; BigInteger one = BigInteger.ONE; one = one.add (null);

Som det viser seg, er det en bedre måte å legge til to på BigIntegers i Kotlin:

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

Dette fungerer fordi Kotlin standardbibliotek selv legger til en god andel utvidelsesoperatører på innebygde typer som BigInteger.

4.2. Andre aritmetiske operatører

Lik i tillegg til, subtraksjon, multiplikasjon, inndeling, og resten jobber på samme måte:

operator moro Punkt.minus (annet: Punkt): Punkt = Punkt (x - annet.x, y - annet.y) operator moro Punkt.tider (annet: Punkt): Punkt = Punkt (x * annet.x, y * annet.y) operator moro Point.div (annet: Punkt): Punkt = Punkt (x / annet.x, y / annet.y) operator moro Punkt.rem (annet: Punkt): Punkt = Punkt (x% annet. x, y% annet.y)

Deretter oversetter Kotlin kompilator ethvert anrop til “-“, “*”, “/”, Eller “%” til "minus", “Ganger”, “Div”, eller “rem” , henholdsvis:

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

Eller hva med å skalere en Punkt med en numerisk faktor:

operator moro Point.times (factor: Int): Point = Point (x * factor, y * factor)

På denne måten kan vi skrive noe sånt som “P1 * 2”:

>> val p1 = Punkt (1, 2) >> println (p1 * 2) Punkt (x = 2, y = 4)

Som vi kan se fra forrige eksempel, er det ingen forpliktelse for to operander å være av samme type. Det samme gjelder returtyper.

4.3. Kommutativitet

Overbelastede operatører er ikke alltid kommutativ. Det er, vi kan ikke bytte operandene og forvente at ting skal fungere så smidig som mulig.

For eksempel kan vi skalere en Punkt ved en integrert faktor ved å multiplisere den med en Int, si “P1 * 2”, men ikke omvendt.

Den gode nyheten er at vi kan definere operatørfunksjoner på Kotlin eller Java innebygde typer. For å gjøre “2 * p1” arbeid, kan vi definere en operatør på Int:

operator moro Int.tider (punkt: Punkt): Punkt = Punkt (punkt.x * dette, punkt.y * dette)

Nå kan vi gjerne bruke “2 * p1” også:

>> val p1 = Punkt (1, 2) >> println (2 * p1) Punkt (x = 2, y = 4)

4.4. Sammensatte oppgaver

Nå som vi kan legge til to BigIntegers med “+” operatør, kan vi kanskje bruke sammensatt oppgave til “+” som er “+=”. La oss prøve denne ideen:

var en = BigInteger.ONE en + = en

Som standard når vi implementerer en av aritmetikkoperatørene, si "i tillegg til", Kotlin støtter ikke bare det kjente “+” operatør, det gjør også det samme for det tilsvarende sammensatt oppgave, som er “+ =”.

Dette betyr at uten mer arbeid kan vi også gjøre:

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) punkt * = 2

Men noen ganger er denne standardoppførselen ikke det vi leter etter. Anta at vi skal bruke “+=” for å legge til et element i en MutableCollection.

For disse scenariene kan vi være eksplisitte om det ved å implementere en operatørfunksjon som heter plusAssign:

operator moro MutableCollection.plusAssign (element: T) {add (element)}

For hver regneoperatør er det en tilsvarende operatør av sammensatt tildeling som alle har "Tildele" suffiks. Det vil si at det er plusAssign, minusAssign, timesAssign, divAssign, og remAssign:

>> valfarger = mutableListOf ("rød", "blå") >> farger + = "grønn" >> println (farger) [rød, blå, grønn]

Alle funksjoner for sammensatt oppgaveoperatør må returnere Enhet.

4.5. Likestilling

Hvis vi overstyrer er lik metoden, så kan vi bruke “==” og “!=” operatørerogså:

klasse Penger (val beløp: BigDecimal, val valuta: Valuta): Sammenlignelig {// utelatt overstyr moro lik (annet: Alle?): Boolsk {hvis (dette === annet) returnerer sant hvis (annet! er penger) returnerer falsk hvis (beløp! = annen.beløp) returnerer falsk hvis (valuta! = annen.valuta) returnerer falsk retur sann} // En tilsvarer kompatibel implementering av hashcode} 

Kotlin oversetter ethvert anrop til “==” og “!=” operatører til en er lik funksjonssamtale, tydeligvis for å lage “!=” arbeid, blir resultatet av funksjonsanrop invertert. Merk at i dette tilfellet trenger vi ikke operatør nøkkelord.

4.6. Sammenligningsoperatører

Det er på tide å baske på BigInteger en gang til!

Anta at vi kjører noen logikk betinget hvis en BigInteger er større enn den andre. I Java er ikke løsningen så ren:

hvis (BigInteger.ONE.compareTo (BigInteger.ZERO)> 0) {// litt logikk}

Når du bruker det samme BigInteger i Kotlin kan vi magisk skrive dette:

hvis (BigInteger.ONE> BigInteger.ZERO) {// samme logikk}

Denne magien er mulig fordi Kotlin har en spesiell behandling av Java Sammenlignelig.

Enkelt sagt, vi kan ringe sammenligne med metoden i Sammenlignelig grensesnitt ved noen få Kotlin-konvensjoner. Faktisk er alle sammenligninger gjort av “<“, “”, eller “>=” vil bli oversatt til en sammenligne med funksjonsanrop.

For å kunne bruke sammenligningsoperatører på en Kotlin-type, må vi implementere den Sammenlignelig grensesnitt:

klasse Penger (val beløp: BigDecimal, val valuta: Valuta): Sammenlignbar {overstyre moro sammenligneTil (annet: Penger): Int = konvertere (Valuta.DOLLARS) .compareTo (other.convert (Currency.DOLLARS)) morsom konvertere (valuta: Valuta): BigDecimal = // utelatt}

Da kan vi sammenligne pengeverdier så enkle som:

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

Siden sammenligne med funksjon i Sammenlignelig grensesnittet er allerede merket med operatør modifikator, trenger vi ikke å legge det til selv.

4.7. I konvensjonen

For å sjekke om et element tilhører en Side, kan vi bruke "i" konvensjon:

operator moro Page.contains (element: T): Boolsk = element i elementer ()

En gang til, kompilatoren ville oversette "i" og "!i" konvensjoner til en funksjon kall til inneholder operatørfunksjon:

>> val side = firstPageOfSomething () >> "Dette" på side >> "Det"! på side

Objektet på venstre side av "i" vil bli sendt som et argument til inneholder og inneholder funksjon ville bli kalt på høyre side operand.

4.8. Få Indexer

Indekserer tillater indeksering av forekomster av en type, akkurat som matriser eller samlinger. Anta at vi skal modellere en paginert samling av elementer som Side, skamløs rive av en idé fra Spring Data:

grensesnitt Side {fun pageNumber (): Int fun pageSize (): Int fun element (): MutableList}

Normalt, for å hente et element fra en Side, vi skal først kalle elementer funksjon:

>> val side = firstPageOfSomething () >> side.elements () [0]

Siden Side i seg selv er bare en fancy innpakning for en annen samling, vi kan bruke indekseringsoperatørene til å forbedre API-en:

operator moro Page.get (indeks: Int): T = elementer () [indeks]

Kotlin-kompilatoren erstatter noen side [indeks] på en Side til en få (indeks) funksjonsanrop:

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

Vi kan gå enda lenger ved å legge til så mange argumenter som vi vil til metodedeklarasjon.

Anta at vi skal hente en del av den innpakket samlingen:

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

Da kan vi skjære a Side som:

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

Også, vi kan bruke alle parametertyper for operatørfunksjon, ikke bare Int.

4.9. Sett Indexer

I tillegg til å bruke indekserer for implementering få-lignende semantikk, vi kan bruke dem til å etterligne settlignende operasjonerogså. Alt vi trenger å gjøre er å definere en navngitt operatørfunksjon sett med minst to argumenter:

operator moro Page.set (indeks: Int, verdi: T) {elementer () [indeks] = verdi}

Når vi erklærer en sett funksjon med bare to argumenter, den første skal brukes inne i braketten og en annen etter oppdrag:

val side: Side = firstPageOfSomething () side [2] = "Noe nytt"

De sett funksjonen kan ha mer enn bare to argumenter. I så fall er den siste parameteren verdien, og resten av argumentene skal sendes innenfor parentesene.

4.10. Påkalle

I Kotlin og mange andre programmeringsspråk er det mulig å påkalle en funksjon med funksjonsnavn (args) syntaks. Det er også mulig å etterligne funksjonssamtalen med påkalle operatørfunksjoner. For eksempel for å kunne bruke side (0) i stedet for side [0] for å få tilgang til det første elementet, kan vi erklære en utvidelse:

operator moro Page.invoke (indeks: Int): T = elementer () [indeks]

Deretter kan vi bruke følgende tilnærming for å hente et bestemt sideelement:

assertEquals (side (1), "Kotlin")

Her oversetter Kotlin parentesene til en samtale til påkalle metode med et passende antall argumenter. Videre kan vi erklære at påkalle operatør med et hvilket som helst antall argumenter.

4.11. Iteratorkonvensjonen

Hva med å gjenta en Side som andre samlinger? Vi må bare erklære en operatørfunksjon som heter iterator med Iterator som returtype:

operator moro Page.iterator () = elementer (). iterator ()

Da kan vi gjenta gjennom en Side:

val page = firstPageOfSomething () for (e på side) {// Gjør noe med hvert element}

4.12. Range Convention

I Kotlin, vi kan lage et område ved hjelp av “..” operatør. For eksempel, “1..42” oppretter et område med tall mellom 1 og 42.

Noen ganger er det fornuftig å bruke rekkeviddeoperatøren på andre ikke-numeriske typer. Kotlins standardbibliotek gir en rekkevidde til konvensjon på alle Sammenlignbare:

operatør moro  T.rangeTo (at: T): ClosedRange = ComparableRange (dette, det)

Vi kan bruke dette til å få noen påfølgende dager som rekkevidde:

val nå = LocalDate.now () val dager = nå..now.plusDays (42)

Som med andre operatører, erstatter Kotlin-kompilatoren noen “..” med en rekkevidde til funksjonsanrop.

5. Bruk operatører med omhu

Overbelastning av operatører er en kraftig funksjon i Kotlin som gjør det mulig for oss å skrive mer konsise og noen ganger mer lesbare koder. Imidlertid, med stor kraft kommer stort ansvar.

Overbelastning av operatører kan gjøre koden vår forvirrende eller til og med vanskelig å lese når det er for ofte brukt eller av og til misbrukt.

Før du legger til en ny operatør til en bestemt type, må du først spørre om operatøren passer semantisk til det vi prøver å oppnå. Eller spør om vi kan oppnå den samme effekten med normale og mindre magiske abstraksjoner.

6. Konklusjon

I denne artikkelen lærte vi mer om mekanikken til operatøroverbelastning i Kotlin og hvordan den bruker et sett med konvensjoner for å oppnå det.

Implementeringen av alle disse eksemplene og kodebiter finner du i GitHub-prosjektet.


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