Üzemeltető túlterhelése Kotlinban

1. Áttekintés

Ebben az oktatóanyagban azokról a konvenciókról fogunk beszélni, amelyeket Kotlin biztosít az operátorok túlterhelésének támogatására.

2. A operátor Kulcsszó

A Java-ban az operátorok meghatározott Java-típusokhoz vannak kötve. Például, Húr és a Java numerikus típusai a + operátort használhatják az összefűzéshez és az összeadáshoz. Semmilyen más Java-típus nem használhatja újra ezt az operátort a saját javára. Kotlin éppen ellenkezőleg, egyezménykészletet nyújt a korlátozottak támogatására Üzemeltető túlterhelése.

Kezdjük egy egyszerűvel adatosztály:

adatosztály Pont (val x: Int, val y: Int)

Néhány operátorral növelni fogjuk ezt az adatosztályt.

Annak érdekében, hogy egy Kotlin függvényt előre definiált névvel operátorrá alakítsunk, meg kell jelölnünk a függvényt a operátor módosító. Például túlterhelhetjük a “+” operátor:

operátor szórakoztató Point.plus (egyéb: Point) = Pont (x + egyéb.x, y + egyéb.y)

Így kettőt adhatunk hozzá Pontok val vel “+”:

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

3. Unary műveletek túlterhelése

Az unári műveletek azok, amelyek csak egy operanduson működnek. Például, -a, a ++ vagy ! a unári műveletek. Általában azok a függvények, amelyek túlterhelik az univerzális operátorokat, nem vesznek fel paramétereket.

3.1. Unary Plus

Mit szólnál a Alak valamiféle kevéssel Pontok:

val s = alakzat {+ Pont (0, 0) + Pont (1, 1) + Pont (2, 2) + Pont (3, 4)}

Kotlinban ez tökéletesen lehetséges a unaryPlus operátor funkció.

Mivel a Alak csak gyűjteménye Pontok, akkor írhatunk osztályt, néhányat becsomagolva Ponts további képességekkel:

osztály Alakzat {private val points = mutableListOf () operátor fun Point.unaryPlus () {points.add (this)}}

És vegye figyelembe, hogy mi adta nekünk a alak {…} a szintaxis az a használata volt Lambda val vel Vevők:

szórakoztató forma (init: Alakzat. () -> Egység): Alakzat {val shape = Alakzat () shape.init () return shape}

3.2. Unary Mínusz

Tegyük fel, hogy van egy Pont nevezett „P” és valami hasonlóval el fogjuk tagadni a koordinációit „-P”. Ezután csak annyit kell tennünk, hogy meghatározzunk egy nevű operátorfüggvényt unaryMinus tovább Pont:

operátor szórakoztató Point.unaryMinus () = Pont (-x, -y)

Ezután, valahányszor hozzáadunk a “-“ előtag a Pont, a fordító lefordítja a unaryMinus függvényhívás:

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

3.3. Növekedés

Minden koordinátát eggyel növelhetünk, csak egy nevű operátorfüggvény végrehajtásával inc:

operátor szórakoztató Point.inc () = Pont (x + 1, y + 1)

A postfix “++” operátor, először visszaadja az aktuális értéket, majd eggyel növeli az értéket:

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

Éppen ellenkezőleg, az előtag “++” operátor, először növeli az értéket, majd visszaadja az újonnan növelt értéket:

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

Is, mivel a “++” operátor újból hozzárendeli az alkalmazott változót, nem tudjuk használni val velük.

3.4. Csökkenés

A növekedéshez hasonlóan csökkenthetjük az egyes koordinátákat a december kezelő funkció:

operátor szórakoztató Point.dec () = Pont (x - 1, y - 1)

december támogatja a dekrementálás előtti és utáni operátorok megszokott szemantikáját, mint a normál numerikus típusok esetében:

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

Szintúgy mint ++ nem használhatjuk val vel vals.

3.5. Nem

Mit szólna ahhoz, ha a koordinátákat csak megfordítanánk ! o? Ezt megtehetjük nem:

operátor szórakozás Point.not () = Pont (y, x)

Egyszerűen fogalmazva: a fordító lefordít bármelyiket „! P” függvényhíváshoz a "nem" unáris operátor funkció:

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

4. Túlterhelés bináris műveletekhez

A bináris operátorok, amint a nevük is mutatja, két operanduson működnek. Tehát a bináris operátorokat túlterhelő függvényeknek legalább egy argumentumot el kell fogadniuk.

Kezdjük a számtani operátorokkal.

4.1. Plusz számtani operátor

Mint korábban láttuk, túlterhelhetjük az alapvető matematikai operátorokat Kotlinban. Tudjuk használni “+” kettőt hozzáadni Pontok együtt:

operátor szórakoztató Pont.plusz (egyéb: Pont): Pont = Pont (x + egyéb.x, y + egyéb.y)

Akkor írhatunk:

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

Mivel plusz egy bináris operátorfüggvény, deklarálnunk kell egy paramétert a függvényhez.

Most a legtöbben megtapasztaltuk a kettő összeadásának hiányosságait BigIntegers:

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

Mint kiderült, van egy jobb módszer kettő hozzáadására BigIntegers Kotlinban:

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

Ez azért működik, mert a A Kotlin standard könyvtár maga hozzáteszi a kiterjesztés-üzemeltetők arányát a beépített típusokhoz hasonlóan BigInteger.

4.2. Egyéb számtani operátorok

Hasonló plusz, kivonás, szorzás, osztály, és a maradék ugyanúgy működnek:

operátor szórakozás Pont.minusz (egyéb: Pont): Pont = Pont (x - egyéb.x, y - egyéb.y) operátor szórakozás Pont.idők (egyéb: Pont): Pont = Pont (x * egyéb.x, y * egyéb.y) operátor fun Point.div (egyéb: Pont): Pont = Pont (x / egyéb.x, y / egyéb.y) operátor szórakozás Pont.rem (egyéb: Pont): Pont = Pont (x% egyéb. x, y% egyéb.y)

Ezután a Kotlin fordító lefordítja a hívást “-“, “*”, „/” Vagy „%” nak nek "mínusz", „Idők”, „Div” vagy „rem” , illetve:

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

Vagy mit szólna a méretezéshez a Pont numerikus tényezővel:

operátor szórakoztató Pont.idők (faktor: Int): Pont = Pont (x * tényező, y * tényező)

Így valami hasonlót írhatunk „P1 * 2”:

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

Amint az előző példából kiderül, két operandus nem köteles azonos típusúak lenni. Ugyanez vonatkozik a visszatérési típusokra is.

4.3. Kommutativitás

A túlterhelt operátorok nem mindig kommutatív. Vagyis nem cserélhetjük az operandusokat, és nem várhatjuk el, hogy a dolgok a lehető legegyszerűbben működjenek.

Például méretezhetjük a Pont integrál tényezővel megszorozva azt egy Int, mond „P1 * 2”, de nem fordítva.

A jó hír az, hogy meghatározhatunk operátorfüggvényeket a Kotlin vagy a Java beépített típusain. Annak érdekében, hogy a „2 * p1” munka esetén definiálhatunk egy operátort Int:

operátor szórakozás Int.times (pont: Pont): Pont = Pont (pont.x * ez, pont.y * ez)

Most már boldogan használhatjuk „2 * p1” is:

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

4.4. Összetett hozzárendelések

Most, hogy kettőt adhatunk hozzá BigIntegers a ... val “+” operátor, akkor használhatjuk az összetett hozzárendelést a “+” ami “+=”. Próbáljuk ki ezt az ötletet:

var one = BigInteger. EGY egy + = one

Alapértelmezés szerint, amikor megvalósítjuk az egyik számtani operátort, mondjuk "plusz", Kotlin nemcsak az ismerőseket támogatja “+” operátor, ugyanazt teszi a megfelelőnek is összetett hozzárendelés, ami „+ =”.

Ez azt jelenti, hogy további munka nélkül a következőket is megtehetjük:

var pont = Pont (0, 0) pont + = Pont (2, 2) pont - = Pont (1, 1) pont * = Pont (2, 2) pont / = Pont (1, 1) pont / = Pont ( 2, 2) pont * = 2

De néha nem ezt az alapértelmezett viselkedést keressük. Tegyük fel, hogy használni fogjuk “+=” elemet adni a MutableCollection.

Ezen forgatókönyvek esetében kifejezetten kifejthetjük róla a nevű operátorfüggvény megvalósítását plusAssign:

operátor szórakozás MutableCollection.plusAssign (elem: T) {add (elem)}

Minden számtani operátorhoz tartozik egy megfelelő összetett hozzárendelési operátor, amelyek mindegyikével rendelkezik "Hozzárendelni" utótag. Vagyis vannak plusAssign, mínuszAssign, timesAssign, divAssign, és remAssign:

>> val színek = mutableListOf ("piros", "kék") >> színek + = "zöld" >> println (színek) [piros, kék, zöld]

Az összes összetett hozzárendelés operátor funkciónak vissza kell térnie Mértékegység.

4.5. Egyenlő Egyezmény

Ha felülírjuk a egyenlő módszerrel, akkor használhatjuk a “==” és “!=” üzemeltetők, is:

osztály Pénz (val összeg: BigDecimal, val pénznem: Pénznem): Összehasonlítható {// kihagyva felülírja a szórakozás egyenlő (egyéb: Bárki?): Logikai érték {ha (ez === egyéb) igaz, ha (más! pénz) hamis if (összeg! = egyéb.összeg) hamis eredményt ad vissza, ha (pénznem! = egyéb. pénznem) hamis visszatérési érték igaz = // egyenlő kompatibilis hashcode implementáció} 

Kotlin lefordítja a hívást “==” és “!=” üzemeltetők egy egyenlő függvényhívás, nyilvánvalóan a “!=” működik, a függvényhívás eredménye megfordul. Vegye figyelembe, hogy ebben az esetben nincs szükségünk a operátor kulcsszó.

4.6. Összehasonlító operátorok

Itt az ideje, hogy lecsapjon BigInteger újra!

Tegyük fel, hogy feltételesen futunk valamilyen logikával, ha van ilyen BigInteger nagyobb, mint a másik. A Java-ban a megoldás nem minden tiszta:

if (BigInteger.ONE.compareTo (BigInteger.ZERO)> 0) {// némi logika}

Ha ugyanazt használja BigInteger Kotlinban varázslatosan megírhatjuk ezt:

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

Ez a varázslat azért lehetséges, mert Kotlin speciálisan kezeli a Java-kat Hasonló.

Egyszerűen fogalmazva hívhatjuk a összehasonlítani módszer a Hasonló interfész néhány Kotlin-egyezmény által. Valójában minden összehasonlítás,<“, “”, vagy “>=” fordítanák a összehasonlítani függvényhívás.

Ahhoz, hogy összehasonlító operátorokat használjunk Kotlin típuson, meg kell valósítanunk azokat Hasonló felület:

osztály Pénz (val összeg: BigDecimal, val pénznem: Pénznem): Összehasonlítható {felülbírálja a szórakozást összehasonlítani (más: Pénz): Int = átvált (Currency.DOLLARS) .compareTo (other.convert (Currency.DOLLARS)) szórakoztató konverzió (pénznem: Pénznem): BigDecimal = // kihagyva}

Ezután összehasonlíthatjuk a monetáris értékeket olyan egyszerűen, mint:

val oneDollar = Pénz (BigDecimal.ONE, Currency.DOLLARS) val tenDollars = Pénz (BigDecimal.TEN, Currency.DOLLARS) if (oneDollar <tenDollars) {// kihagyva}

Mivel a összehasonlítani funkció a Hasonló az interfész már a operátor módosító, nem kell magunknak hozzáadnunk.

4.7. Egyezményben

Annak ellenőrzése érdekében, hogy egy elem tartozik-e a Oldal, használhatjuk a "ban ben" egyezmény:

operátor szórakozás Page.contains (elem: T): Boolean = elem az elemekben ()

Újra, a fordító lefordítaná "ban ben" és "!ban ben" a függvényhívás konvenciói a tartalmazza operátor funkció:

>> val page = firstPageOfSomething () >> "Ez" az oldalon >> "Ez"! az oldalon

A bal oldali objektum "ban ben" érvként továbbításra kerül tartalmazza és a tartalmazza függvényt hívnánk meg a jobb oldali operanduson.

4.8. Szerezd meg az Indexert

Az indexelők lehetővé teszik egy típusú példányok indexelését, akárcsak a tömbök vagy a gyűjtemények. Tegyük fel, hogy egy oldalszámú elemgyűjteményt fogunk modellezni Oldal, szégyentelenül kitépve a Spring Data ötletét:

kezelőfelület oldal {fun pageNumber (): Int fun pageSize (): Int fun elements (): MutableList}

Normális esetben egy elem lekérése az a-ból Oldal, először hívnunk kellene a elemek funkció:

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

Mivel a Oldal maga csak egy fantasztikus csomagoló egy másik gyűjteményhez, az indexelő operátorokat használhatjuk az API továbbfejlesztésére:

operátor szórakozás Page.get (index: Int): T = elemek () [index]

A Kotlin fordító bármelyiket helyettesíti oldal [index] rajta Oldal a get (index) függvényhívás:

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

Még tovább léphetünk, ha annyi érvet adunk hozzá, amennyit csak akarunk kap módszer deklaráció.

Tegyük fel, hogy visszanyerjük a becsomagolt gyűjtemény egy részét:

operátor szórakozás Page.get (kezdet: Int, endExclusive: Int): List = elemek (). subList (start, endExclusive)

Akkor felvághatjuk a Oldal mint:

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

Is, bármilyen paramétertípust használhatunk a kap operátor funkció, nemcsak Int.

4.9. Az Indexelő beállítása

Amellett, hogy indexelőket használ a megvalósításhoz get-szerű szemantika, felhasználhatjuk őket halmazszerű műveletek utánzására, is. Csak annyit kell tennünk, hogy definiálunk egy nevű operátorfüggvényt készlet legalább két érvvel:

operátor szórakoztató Oldal.készlet (index: Int, érték: T) {elemek () [index] = érték}

Amikor kijelentjük a készlet függvény csak két argumentummal, az elsőt a zárójelben kell használni, a másikat pedig a feladat:

val oldal: Page = firstPageOfSomething () oldal [2] = "Valami új"

A készlet A függvénynek nemcsak két argumentuma lehet. Ha igen, akkor az utolsó paraméter az érték, a többi argumentumot pedig a zárójelben kell átadni.

4.10. Invoke

Kotlinban és sok más programozási nyelvben lehívható egy függvény functionName (args) szintaxis. A funkcióhívás szintaxisát a hivatkozni kezelői funkciók. Például a használat érdekében oldal (0) ahelyett [0] oldal az első elem eléréséhez deklarálhatunk kiterjesztést:

operátor szórakozás Page.invoke (index: Int): T = elemek () [index]

Ezután a következő megközelítést használhatjuk egy adott oldalelem lekérésére:

assertEquals (1. oldal, "Kotlin")

Itt Kotlin a zárójeleket lefordítja a hivatkozni módszer megfelelő számú érvvel. Sőt, kijelenthetjük a hivatkozni operátor tetszőleges számú argumentummal.

4.11. Iterátor Egyezmény

Mit szólnál az iteráláshoz a Oldal mint más gyűjtemények? Csak ki kell jelentenünk egy nevű operátorfüggvényt iterátor val vel Iterátor mint visszatérési típus:

operátor szórakozás Page.iterator () = elements (). iterator ()

Ezután iterálhatunk a Oldal:

val page = firstPageOfSomething () for (e az oldalon) {// Tegyen valamit az egyes elemekkel}

4.12. Range Range

Kotlinban, tartományt hozhatunk létre a “..” operátor. Például, “1..42” létrehoz egy tartományt 1 és 42 közötti számokkal.

Néha ésszerű a tartományoperátort használni más, nem numerikus típusoknál. A Kotlin standard könyvtár biztosítja a rangeTo egyezmény Összehasonlíthatók:

üzemeltetői szórakozás  T.rangeTo (hogy: T): ClosedRange = ComparableRange (ez, az)

Ezt felhasználhatjuk arra, hogy néhány egymást követő napot tartományként kapjunk:

val now = LocalDate.now () val days = now..now.plusDays (42)

Mint más operátoroknál, a Kotlin fordító is helyettesít bármelyiket “..” val,-vel rangeTo függvényhívás.

5. Óvatosan használja az operátorokat

A kezelői túlterhelés a Kotlin egyik erőteljes szolgáltatása amely lehetővé teszi számunkra, hogy tömörebb és olykor olvashatóbb kódokat írjunk. Nagy hatalommal azonban nagy felelősség is jár.

Az üzemeltető túlterhelése zavarossá, sőt nehezen olvashatóvá teheti kódunkat amikor túl gyakran használják vagy alkalmanként visszaélnek vele.

Ezért mielőtt új operátort adna hozzá egy adott típushoz, először kérdezze meg, hogy az operátor szemantikailag alkalmas-e arra, amit elérni próbálunk. Vagy kérdezze meg, hogy normális és kevésbé mágikus absztrakciókkal el tudjuk-e érni ugyanazt a hatást?

6. Következtetés

Ebben a cikkben többet megtudhattunk a kezelői túlterhelés mechanikájáról Kotlinban, és arról, hogyan alkalmaz egy sor konvenciót ennek elérésére.

Mindezen példák és kódrészletek megvalósítása megtalálható a GitHub projektben.