Operatør Overbelastning i Kotlin

1. Oversigt

I denne vejledning skal vi tale om de konventioner, som Kotlin giver for at understøtte overbelastning af operatører.

2. Den operatør Nøgleord

I Java er operatører bundet til bestemte Java-typer. For eksempel, Snor og numeriske typer i Java kan bruge + -operatoren til henholdsvis sammenkædning og tilføjelse. Ingen anden Java-type kan genbruge denne operatør til egen fordel. Tværtimod leverer Kotlin et sæt konventioner til støtte for begrænset Operatør Overbelastning.

Lad os starte med en simpel dataklasse:

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

Vi vil forbedre denne dataklasse med et par operatører.

For at omdanne en Kotlin-funktion med et foruddefineret navn til en operatør, vi skal markere funktionen med operatør modifikator. For eksempel kan vi overbelaste “+” operatør:

operator sjov Point.plus (andet: Punkt) = Punkt (x + andet.x, y + andet.y)

På denne måde kan vi tilføje to Point med “+”:

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

3. Overbelastning for unaroperationer

Unary operationer er dem, der fungerer på kun en operand. For eksempel, -a, a ++ eller !en er unære operationer. Generelt tager funktioner, der vil overbelaste unære operatører, ingen parametre.

3.1. Unary Plus

Hvad med at konstruere en Form af en slags med nogle få Point:

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

I Kotlin er det meget muligt med unaryPlus operatørfunktion.

Siden en Form er bare en samling af Point, så kan vi skrive en klasse med nogle få Punkts med evnen til at tilføje flere:

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

Og bemærk, at hvad der gav os form {…} syntaks var at bruge en Lambda med Modtagere:

sjov form (init: Form. () -> Enhed): Form {val form = Form () form.init () returform}

3.2. Unary minus

Antag, at vi har en Punkt som hedder “P” og vi negerer dets koordinationer ved hjælp af noget lignende “-P”. Derefter er alt, hvad vi skal gøre, at definere en navngivet operatorfunktion unaryMinus Punkt:

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

Derefter, hver gang vi tilføjer en “-“ præfikset før en forekomst af Punkt, oversætter compileren det til en unaryMinus funktionsopkald:

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

3.3. Forøgelse

Vi kan forøge hver koordinat med en bare ved at implementere en navngivet operatorfunktion inkl. moms:

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

Postfixet “++” operatør, returnerer først den aktuelle værdi og øger derefter værdien med en:

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

Tværtimod præfikset “++” operator, øger først værdien og returnerer derefter den nyligt forøgede værdi:

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

Også, siden “++” operatøren tildeler den anvendte variabel igen, vi kan ikke bruge val med dem.

3.4. Nedgang

Meget svarende til forøgelse, kan vi mindske hver koordinat ved at implementere dec operatørfunktion:

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

dec understøtter også den velkendte semantik for før- og post-decrement-operatører som for almindelige 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å ligesom ++ vi kan ikke bruge med vals.

3.5. Ikke

Hvad med at vende koordinaterne lige forbi ! s? Vi kan gøre dette med ikke:

operator sjov Punkt.not () = Punkt (y, x)

Kort sagt oversætter compileren enhver “! P” til et funktionsopkald til "ikke" unary operatørfunktion:

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

4. Overbelastning til binære operationer

Binære operatører er, som deres navn antyder, dem, der arbejder på to operander. Så funktioner, der overbelaster binære operatører, skal acceptere mindst et argument.

Lad os starte med de aritmetiske operatorer.

4.1. Plus aritmetisk operatør

Som vi så tidligere, kan vi overbelaste grundlæggende matematiske operatører i Kotlin. Vi kan bruge “+” for at tilføje to Point sammen:

operator sjov Point.plus (andet: Punkt): Punkt = Punkt (x + andet.x, y + andet.y)

Så kan vi skrive:

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

Siden plus er en binær operatorfunktion, skal vi erklære en parameter for funktionen.

Nu har de fleste af os oplevet inelegansen ved at tilføje to sammen BigIntegers:

BigInteger nul = BigInteger.ZERO; BigInteger one = BigInteger.ONE; one = one.add (nul);

Som det viser sig, er der en bedre måde at tilføje to på BigIntegers i Kotlin:

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

Dette fungerer, fordi Kotlin-standardbiblioteket tilføjer selv sin rimelige andel af udvidelsesoperatører på indbyggede typer som BigInteger.

4.2. Andre aritmetiske operatører

Svarende til plus, subtraktion, multiplikation, division, og resten arbejder på samme måde:

operator sjov Punkt.minus (andet: Punkt): Punkt = Punkt (x - andet.x, y - andet.y) operator sjov Punkt.tider (andet: Punkt): Punkt = Punkt (x * andet.x, y * andet.y) operator sjovt Point.div (andet: Punkt): Punkt = Punkt (x / andet.x, y / andet.y) operator sjov Point.rem (andet: Punkt): Punkt = Punkt (x% andet. x, y% andet.y)

Derefter oversætter Kotlin-kompilator ethvert opkald til “-“, “*”, “/” Eller “%” til "minus", “Tider”, "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 hvad med skalering a Punkt med en numerisk faktor:

operator sjov Punkt.tider (faktor: Int): Punkt = Punkt (x * faktor, y * faktor)

På denne måde kan vi skrive noget lignende “P1 * 2”:

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

Som vi kan se fra det foregående eksempel, er der ingen forpligtelse for to operander at være af samme type. Det samme gælder for returtyper.

4.3. Kommutativitet

Overbelastede operatører er ikke altid kommutativ. Det er, vi kan ikke bytte operanderne og forvente, at tingene fungerer så glat som muligt.

For eksempel kan vi skalere en Punkt ved en integreret faktor ved at multiplicere den med en Int, siger “P1 * 2”, men ikke omvendt.

Den gode nyhed er, at vi kan definere operatørfunktioner på Kotlin eller Java indbyggede typer. For at gøre “2 * p1” arbejde, kan vi definere en operatør på Int:

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

Nu kan vi med glæde bruge det “2 * p1” såvel:

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

4.4. Sammensatte opgaver

Nu hvor vi kan tilføje to BigIntegers med “+” operatør, kan vi muligvis bruge sammensatte tildelingen til “+” som er “+=”. Lad os prøve denne idé:

var en = BigInteger.ONE en + = en

Som standard, når vi implementerer en af ​​de aritmetiske operatorer, siger "plus", Kotlin understøtter ikke kun det velkendte “+” operatør, det gør også det samme for det tilsvarende sammensat opgave, som er "+ =".

Dette betyder, at vi uden noget mere arbejde også kan gøre:

var point = Punkt (0, 0) punkt + = Punkt (2, 2) punkt - = Punkt (1, 1) punkt * = Punkt (2, 2) punkt / = Punkt (1, 1) punkt / = Punkt ( 2, 2) punkt * = 2

Men nogle gange er denne standardadfærd ikke det, vi leder efter. Antag, at vi skal bruge “+=” at tilføje et element til en MutableCollection.

For disse scenarier kan vi være eksplicitte om det ved at implementere en navngivet operatorfunktion plusAssign:

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

For hver aritmetiske operator er der en tilsvarende sammensat tildelingsoperator, som alle har "Tildel" suffiks. Det vil sige, der er plusAssign, minusAssign, timesAssign, divAssign, og remAssign:

>> val farver = mutableListOf ("rød", "blå") >> farver + = "grøn" >> println (farver) [rød, blå, grøn]

Alle operatørfunktioner for sammensat tildeling skal vende tilbage Enhed.

4.5. Lige-konvention

Hvis vi tilsidesætter lige med metode, så kan vi bruge “==” og “!=” operatører, også:

klasse Penge (valbeløb: BigDecimal, valuta: Valuta): Sammenlignelig {// udeladt tilsidesættelse af sjov lig (andet: Enhver?): Boolsk {hvis (dette === andet) returnerer sandt hvis (andet! er penge) returnerer falsk hvis (beløb! = andet.beløb) returnerer falsk, hvis (valuta! = anden.valuta) returnerer falsk retur sand} // En er lig med kompatibel hashcode-implementering} 

Kotlin oversætter ethvert opkald til “==” og “!=” operatører til en lige med funktionsopkald, selvfølgelig for at foretage “!=” arbejde, bliver resultatet af funktionsopkald inverteret. Bemærk, at i dette tilfælde behøver vi ikke operatør nøgleord.

4.6. Sammenligningsoperatører

Det er tid til at bash på BigInteger igen!

Antag, at vi kører nogle logiske betingelser, hvis en BigInteger er større end den anden. I Java er løsningen ikke så ren:

hvis (BigInteger.ONE.compareTo (BigInteger.ZERO)> 0) {// noget logik}

Når du bruger det samme BigInteger i Kotlin kan vi på magisk vis skrive dette:

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

Denne magi er mulig fordi Kotlin har en særlig behandling af Java'er Sammenlignelig.

Kort sagt, vi kan ringe til sammenligne med metode i Sammenlignelig interface ved nogle få Kotlin-konventioner. Faktisk er enhver sammenligning foretaget af “<“, “”, eller “>=” ville blive oversat til en sammenligne med funktionskald.

For at kunne bruge sammenligningsoperatorer på en Kotlin-type er vi nødt til at implementere dens Sammenlignelig grænseflade:

klasse Penge (valbeløb: BigDecimal, valuta valuta: Valuta): Sammenlignelig {tilsidesætte sjov sammenlignTil (andet: Penge): Int = konvertere (Valuta.DOLLARS) .compareTo (anden.konvertere (Valuta.DOLLARS)) sjov konvertere (valuta: Valuta): BigDecimal = // udeladt}

Derefter kan vi sammenligne monetære værdier så enkle som:

val oneDollar = Penge (BigDecimal.ONE, Currency.DOLLARS) val tenDollars = Money (BigDecimal.TEN, Currency.DOLLARS) hvis (oneDollar <tenDollars) {// udeladt}

Siden den sammenligne med funktion i Sammenlignelig interface er allerede markeret med operatør modifikator, behøver vi ikke tilføje det selv.

4.7. I konventionen

For at kontrollere, om et element tilhører en Side, kan vi bruge "i" konvention:

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

Igen, ville kompilatoren oversætte "i" og "!i" konventioner til en funktion kald til indeholder operatørfunktion:

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

Objektet på venstre side af "i" vil blive videregivet som et argument til indeholder og indeholder funktion kaldes på højre side operand.

4.8. Få Indexer

Indekserer tillader indeksering af forekomster af en type ligesom arrays eller samlinger. Antag, at vi skal modellere en pagineret samling af elementer som Side, skamløs skåret en idé fra Spring Data:

grænsefladeside {sjov sideNummer (): Int sjov sideSize (): Int sjove elementer (): MutableList}

Normalt for at hente et element fra en Side, vi skal først kalde elementer fungere:

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

Siden den Side i sig selv er bare en fin indpakning til en anden samling, vi kan bruge indekseringsoperatørerne til at forbedre sin API:

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

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

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

Vi kan gå endnu længere ved at tilføje så mange argumenter som vi vil til metodedeklaration.

Antag, at vi henter en del af den indpakket samling:

operatør sjov Page.get (start: Int, endExclusive: Int): List = elements (). subList (start, endExclusive)

Så kan vi skære a Side synes godt om:

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

Også, vi kan bruge alle parametertyper til operatørfunktion, ikke kun Int.

4.9. Indstil indekser

Ud over at bruge indekserer til implementering få-lignende semantik, vi kan bruge dem til at efterligne sæt-lignende operationer, også. Alt, hvad vi skal gøre er at definere en navngivet operatorfunktion sæt med mindst to argumenter:

operatør sjov Page.set (indeks: Int, værdi: T) {elementer () [indeks] = værdi}

Når vi erklærer en sæt funktion med kun to argumenter, den første skal bruges inden i parentes og en anden efter opgave:

val side: Side = firstPageOfSomething () side [2] = "Noget nyt"

Det sæt funktion kan også have mere end to argumenter. Hvis ja, er den sidste parameter værdien, og resten af ​​argumenterne skal sendes inden for parenteserne.

4.10. Påkald

I Kotlin og mange andre programmeringssprog er det muligt at påberåbe sig en funktion med funktionsnavn (args) syntaks. Det er også muligt at efterligne syntaksfunktionens syntaks med påberåbe sig operatørfunktioner. For eksempel for at bruge side (0) i stedet for side [0] for at få adgang til det første element kan vi erklære en udvidelse:

operatør sjov Page.invoke (indeks: Int): T = elementer () [indeks]

Derefter kan vi bruge følgende tilgang til at hente et bestemt sideelement:

assertEquals (side (1), "Kotlin")

Her oversætter Kotlin parenteserne til et opkald til påberåbe sig metode med et passende antal argumenter. Desuden kan vi erklære påberåbe sig operatør med et hvilket som helst antal argumenter.

4.11. Iterator-konventionen

Hvad med iterering af en Side ligesom andre samlinger? Vi skal bare erklære en operatørfunktion navngivet iterator med Iterator som returtype:

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

Så kan vi gentage gennem en Side:

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

4.12. Range Convention

I Kotlin, vi kan oprette et interval ved hjælp af “..” operatør. For eksempel, “1..42” opretter et interval med tal mellem 1 og 42.

Nogle gange er det fornuftigt at bruge rækkeviddeoperatøren på andre ikke-numeriske typer. Kotlins standardbibliotek giver en rækkevidde til konvention om alle Sammenlignelige:

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

Vi kan bruge dette til at få et par dage i træk som et interval:

val nu = LocalDate.now () val dage = nu..now.plusDays (42)

Som med andre operatører erstatter Kotlin-kompilatoren nogen “..” med en rækkevidde til funktionskald.

5. Brug operatører med omhu

Operatøroverbelastning er en stærk funktion i Kotlin hvilket gør det muligt for os at skrive mere kortfattede og nogle gange mere læsbare koder. Imidlertid kommer stort ansvar med stor kraft.

Operatøroverbelastning kan gøre vores kode forvirrende eller endda svær at læse når det bruges for ofte eller lejlighedsvis misbruges.

Inden du tilføjer en ny operatør til en bestemt type, skal du først spørge, om operatøren passer semantisk til det, vi prøver at opnå. Eller spørg om vi kan opnå den samme effekt med normale og mindre magiske abstraktioner.

6. Konklusion

I denne artikel lærte vi mere om mekanikken ved operatøroverbelastning i Kotlin, og hvordan den bruger et sæt konventioner til at opnå det.

Implementeringen af ​​alle disse eksempler og kodestykker findes i GitHub-projektet.


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