Generički lijekovi u Kotlinu

1. Pregled

U ovom ćemo članku pogledati generički tipovi u jeziku Kotlin.

Vrlo su slični onima iz jezika Java, ali tvorci jezika Kotlin pokušali su ih učiniti malo intuitivnijima i razumljivijima uvođenjem posebnih ključnih riječi poput van i u.

2. Stvaranje parametariziranih klasa

Recimo da želimo stvoriti parametriziranu klasu. To možemo lako učiniti na jeziku Kotlin pomoću generičkih vrsta:

klasa ParameterizedClass (privatna vrijednost valu: A) {fun getValue (): A {povratna vrijednost}}

Primjer takve klase možemo stvoriti postavljanjem parametriziranog tipa izričito kada koristimo konstruktor:

val parameterizedClass = ParameterizedClass ("string-value") val res = parameterizedClass.getValue () assertTrue (res je String)

Srećom, Kotlin može generički tip zaključiti iz tipa parametra, tako da to možemo izostaviti kada koristimo konstruktor:

val parameterizedClass = ParameterizedClass ("string-value") val res = parameterizedClass.getValue () assertTrue (res je String)

3. Kotlin van i u Ključne riječi

3.1. The Van Ključna riječ

Recimo da želimo stvoriti klasu proizvođača koja će proizvoditi rezultat neke vrste T. Ponekad; želimo proizvedenu vrijednost dodijeliti referenci koja je supertipa tipa T.

Da bi to postigli koristeći Kotlin, trebamo koristitivan ključna riječ na generičkom tipu. To znači da ovu referencu možemo dodijeliti bilo kojem od njezinih nadtipova. Izlaznu vrijednost može proizvesti samo određena klasa, ali ne i potrošiti:

class ParameterizedProducer (vrijednost privatne valute: T) {fun get (): T {povratna vrijednost}}

Definirali smo a ParameterizedProducer klasa koja može proizvesti vrijednost tipa T.

Sljedeći; možemo dodijeliti primjerak ParameterizedProducer klase na referencu koja je njen supertip:

val parameterizedProducer = ParameterizedProducer ("string") val ref: ParameterizedProducer = parameterizedProducer assertTrue (ref je ParameterizedProducer)

Ako je vrsta T u Paramaterizirani proizvođač razred neće biti van tipa, dana izjava će proizvesti pogrešku kompajlera.

3.2. The u Ključna riječ

Ponekad imamo suprotnu situaciju što znači da imamo referencu tipa T i želimo ga moći dodijeliti podtipu T.

Možemo koristiti u ključnu riječ na generičkom tipu ako je želimo dodijeliti referenci svoje podvrste. The u ključnoj riječi može se koristiti samo za tip parametra koji se troši, a ne proizvodi:

klasa ParameterizedConsumer {fun toString (value: T): String {return value.toString ()}}

Izjavljujemo da a toString () metoda troši samo vrijednost tipa T.

Dalje, možemo dodijeliti referencu tipa Broj na referencu svoje podvrste - Dvostruko:

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

Ako je vrsta T u ParameterizedCounsumer neće biti u tipa, dana izjava će proizvesti pogrešku kompajlera.

4. Upišite projekcije

4.1. Kopirajte niz podtipova u niz supertipova

Recimo da imamo niz neke vrste i želimo kopirati cijeli niz u niz Bilo koji tip. To je valjana operacija, ali da bismo kompajleru omogućili kompajliranje našeg koda moramo ulazni parametar označiti s van ključna riječ.

To daje prevoditelju do znanja da ulazni argument može biti bilo koje vrste koja je podvrsta Bilo koji:

zabavna kopija (od: Array, do: Array) {assert (from.size == to.size) for (i in from.indices) to [i] = from [i]}

Ako je iz parametar nije od van Bilo koji tipa, nećemo moći proslijediti niz Int vrsta kao 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. Dodavanje elemenata podtipa nizu njegovih supertipova

Recimo da imamo sljedeću situaciju - imamo niz Bilo koji tip koji je supertip Int i želimo dodati Int element ovog niza. Moramo koristiti u ključna riječ kao vrsta odredišnog niza kako bi prevoditelju dao do znanja da možemo kopirati Int vrijednost za ovaj niz:

zabavno popunjavanje (dest: Niz, vrijednost: Int) {dest [0] = vrijednost}

Zatim možemo kopirati vrijednost Int upišite u polje Bilo koji:

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

4.3. Zvjezdane projekcije

Postoje situacije kada nam nije stalo do određene vrste vrijednosti. Recimo da samo želimo ispisati sve elemente niza i nije važno koja je vrsta elemenata u ovom nizu.

Da bismo to postigli, možemo koristiti projekciju zvijezde:

zabavno printArray (niz: Array) {array.forEach {println (it)}}

Zatim, možemo poslati niz bilo kojeg tipa na printArray () metoda:

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

Kada koristimo referentni tip projekcije zvijezde, možemo čitati vrijednosti s njega, ali ih ne možemo zapisati jer će to uzrokovati pogrešku u kompilaciji.

5. Generička ograničenja

Recimo da želimo sortirati niz elemenata, a svaki tip elementa trebao bi implementirati a Usporedive sučelje. Generička ograničenja možemo koristiti za specificiranje tog zahtjeva:

zabava  sort (list: List): List {return list.sorted ()}

U danom primjeru definirali smo da svi elementi T potrebno za provedbu Usporedive sučelje. Inače, ako pokušamo proslijediti popis elemenata koji ne implementiraju ovo sučelje, to će uzrokovati pogrešku kompajlera.

Definirali smo a vrsta funkcija koja uzima kao argument popis elemenata koji se provode Usporedivo, pa možemo nazvati sortirano () metoda na njemu. Pogledajmo test slučaja za tu metodu:

val listOfInts = listOf (5,2,3,4,1) val sorted = sort (listOfInts) assertEquals (sorted, listOf (1,2,3,4,5))

Lako možemo proslijediti popis Ints jer Int tip provodi Usporedive sučelje.

5.1. Višestruke gornje granice

Oznakom kutnih zagrada možemo proglasiti najviše jednu generičku gornju granicu. Ako parametar tipa treba više generičkih gornjih granica, tada bismo trebali koristiti zasebne gdje klauzule za taj određeni parametar tipa. Na primjer:

zabavno sortiranje (xs: Popis) gdje je T: CharSequence, T: Usporedivo {// sortiranje kolekcije na mjestu}

Kao što je gore prikazano, parametar T mora provesti CharSequence i Usporedive sučelja istovremeno. Slično tome, možemo deklarirati klase s više generičkih gornjih granica:

klasa StringCollection (xs: List) gdje je T: CharSequence, T: Usporedivo {// izostavljeno}

6. Generički lijekovi u vrijeme izvođenja

6.1. Tip Brisanje

Kao i kod Jave, Kotlinovi generički lijekovi jesu izbrisani za vrijeme izvođenja. To je, instanca generičke klase ne čuva svoje parametre tipa za vrijeme izvođenja.

Na primjer, ako kreiramo Postavi i u nju stavimo nekoliko žica, u vrijeme izvođenja možemo je vidjeti samo kao Postavi.

Stvorimo dvije Kompleti s dva različita parametra tipa:

val knjige: Set = setOf ("1984", "Hrabri novi svijet") val prime: Set = setOf (2, 3, 11)

Tijekom izvođenja, informacije o tipu za Postavi i Postavi bit će izbrisani i oboje ih vidimo kao obične Kompleti. Dakle, iako je savršeno moguće tijekom izvođenja saznati je li vrijednost a Postavi, ne možemo reći je li to Postavi nizova, cijelih brojeva ili nečeg drugog: da su podaci izbrisani.

Pa, kako nas Kotlinov kompajler sprečava da dodamo a Ne-nizanje u a Postavi? Ili, kada element dobijemo iz a Postavi, kako zna da je element a Niz?

Odgovor je jednostavan. Prevoditelj je odgovoran za brisanje podataka o tipu ali prije toga, zapravo zna knjige varijabla sadrži Niz elementi.

Dakle, svaki put kad od njega dobijemo element, kompajler bi ga prebacio u Niz ili kad ćemo u njega dodati element, kompajler bi upisao check input.

6.2. Reificirani parametri tipa

Zabavimo se više s generičkim lijekovima i stvorimo funkciju proširenja za filtriranje Kolekcija elementi na temelju njihove vrste:

fun Iterable.filterIsInstance () = filter {to je T} Pogreška: Nije moguće provjeriti na primjer izbrisani tip: T

"to je T " dio, za svaki element kolekcije, provjerava je li element primjer tipa T, ali budući da su informacije o tipu izbrisane tijekom izvođenja, ne možemo na ovaj način odražavati parametre tipa.

Ili možemo?

Pravilo brisanja tipa općenito vrijedi, ali postoji jedan slučaj u kojem možemo izbjeći ovo ograničenje: Inline funkcije. Tipski parametri ugrađenih funkcija mogu biti reificirano, tako da se možemo obratiti tim parametrima tipa tijekom izvođenja.

Tijelo inline funkcija je ucrtano. Odnosno, prevodilac supstituira tijelo izravno na mjesta gdje se funkcija poziva umjesto uobičajenog poziva funkcije.

Ako prethodnu funkciju deklariramo kao u redu i označite parametar tipa kao reificirano, tada možemo pristupiti informacijama generičkog tipa za vrijeme izvođenja:

inline fun Iterable.filterIsInstance () = filter {to je T}

Ugrađena reifikacija djeluje poput šarma:

>> val set = setOf ("1984", 2, 3, "Hrabri novi svijet", 11) >> println (set.filterIsInstance ()) [2, 3, 11]

Napišimo još jedan primjer. Svi su nam poznati tipični SLF4j Drvosječa definicije:

klasa Korisnik {private val log = LoggerFactory.getLogger (Korisnik :: class.java) // ...}

Korištenjem reificiranih ugrađenih funkcija možemo pisati elegantnije i manje zastrašujuće sintakse Drvosječa definicije:

ugrađeni zapisnik za zabavu (): Logger = LoggerFactory.getLogger (T :: class.java)

Tada možemo napisati:

klasa Korisnik {private val log = logger () // ...}

To nam daje čistiju opciju za primjenu sječe, na Kotlin način.

6.3. Duboko zaronite u linijsku reifikaciju

Pa, što je tako posebno u ugrađenim funkcijama, tako da reifikacija tipa radi samo s njima? Kao što znamo, Kotlinov kompajler kopira bajtkod ugrađenih funkcija na mjesta na kojima se funkcija poziva.

Budući da na svakom mjestu poziva, kompajler zna točan tip parametra, on može zamijeniti generički parametar tipa stvarnim referencama tipa.

Na primjer, kada napišemo:

klasa Korisnik {private val log = logger () // ...}

Kada kompajler stavi drvosječa() poziv funkcije, zna stvarni parametar generičkog tipa -Korisnik. Dakle, umjesto brisanja podataka o tipu, sastavljač koristi priliku za reifikaciju i reificira stvarni parametar tipa.

7. Zaključak

U ovom smo članku gledali generičke vrste Kotlin. Vidjeli smo kako se koristi van i u pravilno ključne riječi. Koristili smo projekcije tipova i definirali generičku metodu koja koristi generička ograničenja.

Provedbu svih ovih primjera i isječaka koda možete pronaći u projektu GitHub - ovo je Maven projekt, pa bi ga trebalo lako uvesti i pokrenuti kakav jest.