Generiká v Kotline

1. Prehľad

V tomto článku sa pozrieme na generické typy v jazyku Kotlin.

Sú veľmi podobné tým z jazyka Java, ale tvorcovia jazyka Kotlin sa ich pokúsili urobiť trochu intuitívnejšími a zrozumiteľnejšími zavedením špeciálnych kľúčových slov ako von a v.

2. Vytváranie parametrizovaných tried

Povedzme, že chceme vytvoriť parametrizovanú triedu. V jazyku Kotlin to môžeme ľahko urobiť pomocou všeobecných typov:

trieda ParameterizedClass (hodnota súkromnej hodnoty: A) {fun getValue (): A {návratová hodnota}}

Inštanciu takejto triedy môžeme vytvoriť explicitným nastavením parametrizovaného typu pri použití konštruktora:

val parameterizedClass = ParameterizedClass ("hodnota-reťazca") val res = parametizedClass.getValue () assertTrue (res je reťazec)

Našťastie môže Kotlin odvodiť generický typ z typu parametra, takže ho pri použití konštruktora môžeme vynechať:

val parameterizedClass = ParameterizedClass ("hodnota-reťazca") val res = parametizedClass.getValue () assertTrue (res je reťazec)

3. Kotlin von a v Kľúčové slová

3.1. The Von Kľúčové slovo

Povedzme, že chceme vytvoriť triedu producentov, ktorá bude produkovať výsledok nejakého typu T. Niekedy; chceme priradiť túto vyprodukovanú hodnotu k odkazu, ktorý je supertypu typu T.

Aby sme to dosiahli pomocou Kotlina, musíme použiťvon kľúčové slovo na všeobecnom type. To znamená, že môžeme tento odkaz priradiť k niektorému z jeho supertypov. Výstupnú hodnotu môže vytvoriť iba daná trieda, ale nesmie sa spotrebovať:

trieda ParameterizedProducer (hodnota súkromnej hodnoty: T) {fun get (): T {návratová hodnota}}

Definovali sme a ParameterizedProducer triedy, ktorá dokáže vyprodukovať hodnotu typu T.

Ďalšie; môžeme priradiť inštanciu súboru ParameterizedProducer triedy na referenciu, ktorá je jej nadtypom:

val parameterizedProducer = ParameterizedProducer ("reťazec") val ref: ParameterizedProducer = parametizedProducer assertTrue (ref je ParameterizedProducer)

Ak typ T v Parametrizovaný výrobca trieda nebude von typu, daný príkaz vyprodukuje chybu prekladača.

3.2. The v Kľúčové slovo

Niekedy máme opačnú situáciu, čo znamená, že máme referenciu typu T a chceme mať možnosť priradiť ho k podtypu T.

Môžeme použiť v kľúčové slovo na generický typ, ak ho chceme priradiť k odkazu jeho podtypu. The v kľúčovom slove je možné použiť iba na typ parametra, ktorý sa spotrebúva, nie je vyrobený:

trieda ParameterizedConsumer {fun toString (hodnota: T): Reťazec {návratová hodnota.toString ()}}

Vyhlasujeme, že a natiahnuť() metóda bude konzumovať iba hodnotu typu T.

Ďalej môžeme priradiť referenciu typu Číslo na odkaz na jeho podtyp - Dvojité:

val parameterizedConsumer = ParameterizedConsumer () val ref: ParameterizedConsumer = parameterizedConsumer assertTrue (ref je ParameterizedConsumer)

Ak typ T v ParameterizedCounsumer nebude v typu, daný príkaz vyprodukuje chybu prekladača.

4. Zadajte projekcie

4.1. Skopírujte pole podtypov do poľa supertypov

Povedzme, že máme pole nejakého typu a chceme celé pole skopírovať do poľa akýkoľvek typu. Je to platná operácia, ale aby sme umožnili kompilátoru zostaviť náš kód, musíme anotovať vstupný parameter pomocou von kľúčové slovo.

Toto dáva kompilátoru vedieť, že vstupný argument môže byť ľubovoľného typu, ktorý je podtypom akýkoľvek:

zábavná kópia (z: Array, do: Array) {assert (from.size == to.size) pre (i v from.indices) do [i] = z [i]}

Ak od parameter nie je z von Akýkoľvek typu, nebudeme schopní odovzdať pole typu Int zadajte ako argument:

val ints: Array = arrayOf (1, 2, 3) val any: Array = arrayOfNulls (3) copy (ints, any) assertEquals (any [0], 1) assertEquals (any [1], 2) assertEquals (any [ 2], 3)

4.2. Pridanie prvkov podtypu do poľa jeho supertypu

Povedzme, že máme nasledujúcu situáciu - máme pole akýkoľvek typ, ktorý je supertypom Int a chceme pridať Int prvku do tohto poľa. Musíme použiť v kľúčové slovo ako typ cieľového poľa, aby kompilátor vedel, že môžeme skopírovať Int hodnotu tohto poľa:

fun fill (dest: Array, value: Int) {dest [0] = value}

Potom môžeme skopírovať hodnotu súboru Int zadajte do poľa Akýkoľvek:

objekty val: Array = arrayOfNulls (1) fill (objekty, 1) assertEquals (objekty [0], 1)

4.3. Hviezdne projekcie

Existujú situácie, keď sa nestaráme o konkrétny typ hodnoty. Povedzme, že chceme iba vytlačiť všetky prvky poľa a nezáleží na tom, aký typ prvkov v tomto poli je.

Aby sme to dosiahli, môžeme použiť hviezdicovú projekciu:

fun printArray (pole: Array) {array.forEach {println (it)}}

Potom môžeme do poľa odovzdať pole ľubovoľného typu printArray () metóda:

val pole = arrayOf (1,2,3) printArray (pole)

Pri použití referenčného typu hviezdnej projekcie z neho môžeme načítať hodnoty, ale nemôžeme ich zapisovať, pretože by to spôsobilo chybu kompilácie.

5. Všeobecné obmedzenia

Povedzme, že chceme zoradiť pole prvkov a každý typ prvku by mal implementovať a Porovnateľné rozhranie. Na určenie tejto požiadavky môžeme použiť všeobecné obmedzenia:

zábava  sort (list: List): List {return list.sorted ()}

V danom príklade sme definovali, že všetky prvky T potrebné na implementáciu Porovnateľné rozhranie. V opačnom prípade, ak sa pokúsime odovzdať zoznam prvkov, ktoré toto rozhranie neimplementujú, spôsobí to chybu kompilátora.

Definovali sme a triediť funkcia, ktorá berie ako argument zoznam prvkov, ktoré implementujú Porovnateľný aby sme mohli zavolať zoradené () metóda na to. Pozrime sa na testovací prípad tejto metódy:

val listOfInts = listOf (5,2,3,4,1) val seřazené = zoradiť (listOfInts) assertEquals (zoradené, listOf (1,2,3,4,5))

Ľahko môžeme odovzdať zoznam Ints pretože Int typ implementuje Porovnateľné rozhranie.

5.1. Viacero horných hraníc

Pomocou zápisu lomenej zátvorky môžeme deklarovať najviac jednu všeobecnú hornú hranicu. Ak parameter typu vyžaduje viac všeobecných horných hraníc, mali by sme použiť samostatné kde klauzuly pre daný konkrétny parameter typu. Napríklad:

zábavné triedenie (xs: List), kde T: CharSequence, T: porovnateľné {// triediť zbierku na danom mieste}

Ako je uvedené vyššie, parameter T musí implementovať CharSequence a Porovnateľné rozhrania súčasne. Podobne môžeme deklarovať triedy s viacerými všeobecnými hornými hranicami:

trieda StringCollection (xs: List), kde T: CharSequence, T: porovnateľná {// vynechaná}

6. Generiká za behu

6.1. Typ Vymazať

Rovnako ako v prípade Java, aj Kotlinove generiká sú vymazané za behu. To znamená, inštancia generickej triedy si za behu nezachová svoje parametre typu.

Napríklad, ak vytvoríme a Nastaviť a vložíme doň niekoľko reťazcov, za behu modulu to môžeme vidieť iba ako Nastaviť.

Vytvorme dve Sady s dvoma rôznymi typovými parametrami:

val knihy: Set = setOf ("1984", "Brave new world") val prvočísla: Set = setOf (2, 3, 11)

Počas behu zadajte informácie o type pre Nastaviť a Nastaviť budú vymazané a obidve vidíme ako obyčajné Sady. Takže aj keď je úplne možné za behu zistiť, že hodnota je Nastaviť, nemôžeme povedať, či je to Nastaviť reťazcov, celých čísel alebo niečoho iného: tieto informácie boli vymazané.

Ako nám teda Kotlinov kompilátor bráni v pridaní a Non-String do a Nastaviť? Alebo keď dostaneme prvok z a Nastaviť, ako to vie, že prvok je a String?

Odpoveď je jednoduchá. Kompilátor je zodpovedný za vymazanie typovej informácie ale predtým to vlastne pozná kníh premenná obsahuje String prvkov.

Zakaždým, keď z neho dostaneme prvok, kompilátor ho vrhne na a String alebo keď do nej pridáme prvok, kompilátor by zadal kontrolu vstupu.

6.2. Zjednotené parametre typu

Poďme sa viac baviť s generikami a vytvorme si funkciu rozšírenia na filtrovanie Zbierka prvky založené na ich type:

fun Iterable.filterIsInstance () = filter {it is T} Chyba: Nemôžem skontrolovať napríklad vymazaný typ: T

to je T ” časť pre každý element kolekcie skontroluje, či je element inštanciou typu T, ale keďže informácie o type boli za behu vymazané, nemôžeme takto uvažovať o parametroch typu.

Alebo môžeme?

Pravidlo vymazania typu je všeobecne platné, ale existuje jeden prípad, keď sa môžeme vyhnúť tomuto obmedzeniu: Inline funkcie. Parametre typu vložených funkcií môžu byť reifikovaný, takže na tieto parametre typu môžeme za behu odkazovať.

Telo vložených funkcií je vložené. To znamená, že kompilátor nahradí telo priamo na miesta, kde sa funkcia volá, namiesto vyvolania normálnej funkcie.

Ak deklarujeme predchádzajúcu funkciu ako v rade a označte parameter typu ako reifikovaný, potom môžeme za behu získať prístup k informáciám o všeobecnom type:

vložená zábava Iterable.filterIsInstance () = filter {je T}

Vložené zjednotenie funguje ako kúzlo:

>> val set = setOf ("1984", 2, 3, "Brave new world", 11) >> println (set.filterIsInstance ()) [2, 3, 11]

Napíšme ďalší príklad. Všetci poznáme tie typické SLF4j Logger definície:

trieda User {private val log = LoggerFactory.getLogger (User :: class.java) // ...}

Pomocou zjednotených vložených funkcií môžeme písať elegantnejšie a menej hrôzostrašne Logger definície:

vložený zábavný záznamník (): Logger = LoggerFactory.getLogger (T :: class.java)

Potom môžeme napísať:

trieda Užívateľ {private val log = logger () // ...}

To nám dáva čistejšiu možnosť implementovať ťažbu dreva, spôsobom Kotlin.

6.3. Hlboko sa ponorte do lineárneho zjednotenia

Čo je teda také zvláštne na vložených funkciách, aby reifikácia typu fungovala iba s nimi? Ako vieme, Kotlinov prekladač kopíruje bajtkód vložených funkcií na miesta, kde sa funkcia volá.

Pretože na každej stránke volania vie kompilátor presný typ parametra, môže nahradiť parameter generického typu skutočnými odkazmi na typ.

Napríklad, keď napíšeme:

trieda Užívateľ {private val log = logger () // ...}

Keď kompilátor riadky záznamník () volanie funkcie, pozná skutočný parameter generického typu -Používateľ. Takže namiesto toho, aby vymazal informácie o type, kompilátor využije príležitosť na opätovné zjednotenie a opraví skutočný parameter typu.

7. Záver

V tomto článku sme sa zaoberali generickými typmi Kotlin. Videli sme, ako používať von a v kľúčové slová správne. Použili sme projekcie typov a definovali sme všeobecnú metódu, ktorá používa všeobecné obmedzenia.

Implementáciu všetkých týchto príkladov a útržkov kódu nájdete v projekte GitHub - jedná sa o projekt Maven, takže by malo byť ľahké ho importovať a spustiť tak, ako je.


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