Generieke geneesmiddelen in Kotlin

1. Overzicht

In dit artikel zullen we kijken naar de generieke typen in de Kotlin-taal.

Ze lijken erg op die uit de Java-taal, maar de makers van de Kotlin-taal probeerden ze een beetje intuïtiever en begrijpelijker te maken door speciale zoekwoorden in te voeren, zoals uit en in.

2. Geparameteriseerde klassen creëren

Laten we zeggen dat we een klasse met parameters willen maken. We kunnen dit gemakkelijk doen in de Kotlin-taal door generieke typen te gebruiken:

class ParameterizedClass (persoonlijke waarde: A) {fun getValue (): A {retourwaarde}}

We kunnen een instantie van zo'n klasse maken door expliciet een geparametriseerd type in te stellen bij gebruik van de constructor:

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

Gelukkig kan Kotlin het generieke type afleiden uit het parametertype, zodat we dat kunnen weglaten bij het gebruik van de constructor:

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

3. Kotlin uit en in Sleutelwoorden

3.1. De Uit Trefwoord

Laten we zeggen dat we een producer-klasse willen creëren die een resultaat zal produceren van een soort T. Soms; we willen die geproduceerde waarde toewijzen aan een referentie die van een supertype is van het type T.

Om dat te bereiken met Kotlin, we moeten deuit trefwoord op het generieke type. Het betekent dat we deze verwijzing aan elk van zijn supertypes kunnen toewijzen. De uit-waarde kan alleen worden geproduceerd door de gegeven klasse, maar niet worden verbruikt:

class ParameterizedProducer (persoonlijke waarde: T) {fun get (): T {retourwaarde}}

We hebben een ParameterizedProducer klasse die een waarde van het type T kan produceren.

De volgende; we kunnen een instantie van de ParameterizedProducer klasse naar de referentie die er een supertype van is:

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

Als het type T in de Geparameteriseerde Producent klasse zal niet de uit type, zal de gegeven instructie een compilatiefout produceren.

3.2. De in Trefwoord

Soms hebben we een tegenovergestelde situatie, wat betekent dat we een soort referentie hebben T en we willen het kunnen toewijzen aan het subtype van T.

We kunnen de in trefwoord op het generieke type als we het willen toewijzen aan de referentie van het subtype. De in trefwoord kan alleen worden gebruikt op het parametertype dat wordt verbruikt, niet geproduceerd:

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

We verklaren dat a toString () methode verbruikt alleen een waarde van het type T.

Vervolgens kunnen we een referentie van het type toewijzen Aantal naar de referentie van zijn subtype - Dubbele:

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

Als het type T in de ParameterizedCounsumer zal niet de in type, zal de gegeven instructie een compilatiefout produceren.

4. Typ projecties

4.1. Kopieer een array met subtypen naar een array met supertypen

Laten we zeggen dat we een array van een bepaald type hebben, en we willen de hele array kopiëren naar de array van Ieder type. Het is een geldige bewerking, maar om de compiler in staat te stellen onze code te compileren, moeten we de invoerparameter annoteren met de uit trefwoord.

Dit laat de compiler weten dat het invoerargument van elk type kan zijn dat een subtype is van de Ieder:

leuke kopie (from: Array, to: Array) {assert (from.size == to.size) voor (i in from.indices) naar [i] = van [i]}

Als het van parameter is niet van de uit Elke type, kunnen we een array van een Int typ als een 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. Elementen van een subtype toevoegen aan een array van zijn supertype

Laten we zeggen dat we de volgende situatie hebben - we hebben een reeks van Ieder type dat een supertype is van Int en we willen een Int element toe aan deze array. We moeten de in trefwoord als een type van de doelmatrix om de compiler te laten weten dat we de Int waarde toe aan deze array:

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

Vervolgens kunnen we een waarde van de Int typ naar de array van Ieder:

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

4.3. Sterprojecties

Er zijn situaties waarin we niet geven om het specifieke type waarde. Laten we zeggen dat we gewoon alle elementen van een array willen afdrukken en het maakt niet uit wat het type elementen in deze array is.

Om dat te bereiken, kunnen we een sterprojectie gebruiken:

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

Vervolgens kunnen we een array van elk type doorgeven aan de printArray () methode:

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

Wanneer we het referentietype sterprojectie gebruiken, kunnen we er waarden uit lezen, maar we kunnen ze niet schrijven omdat dit een compilatiefout zal veroorzaken.

5. Algemene beperkingen

Laten we zeggen dat we een reeks elementen willen sorteren en dat elk elementtype een Vergelijkbaar koppel. We kunnen de generieke beperkingen gebruiken om die vereiste te specificeren:

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

In het gegeven voorbeeld hebben we gedefinieerd dat alle elementen T nodig om het Vergelijkbaar koppel. Anders, als we proberen een lijst met elementen door te geven die deze interface niet implementeren, zal dit een compilatiefout veroorzaken.

We hebben een soort functie die als argument een lijst met elementen aanneemt die implementeren Vergelijkbaar, dus we kunnen de gesorteerd () methode erop. Laten we eens kijken naar de testcase voor die methode:

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

We kunnen gemakkelijk een lijst doorgeven van Ints omdat de Int type implementeert het Vergelijkbaar koppel.

5.1. Meerdere bovengrenzen

Met de punthaaknotatie kunnen we maximaal één generieke bovengrens aangeven. Als een typeparameter meerdere generieke bovengrenzen nodig heeft, moeten we separate waar clausules voor die specifieke typeparameter. Bijvoorbeeld:

fun sort (xs: List) waarbij T: CharSequence, T: Comparable {// sorteer de collectie op zijn plaats}

Zoals hierboven getoond, is de parameter T moet het CharSequence en Vergelijkbaar interfaces tegelijkertijd. Evenzo kunnen we klassen declareren met meerdere generieke bovengrenzen:

class StringCollection (xs: List) waarbij T: CharSequence, T: Comparable {// weggelaten}

6. Generics tijdens Runtime

6.1. Typ Wissen

Net als bij Java zijn de generieke geneesmiddelen van Kotlin dat wel gewist tijdens runtime. Dat is, een instantie van een generieke klasse behoudt zijn typeparameters niet tijdens runtime.

Als we bijvoorbeeld een Set en steek er een paar snaren in, tijdens runtime kunnen we het alleen zien als een Set.

Laten we er twee maken Sets met twee verschillende typeparameters:

val books: Set = setOf ("1984", "Brave new world") val priemgetallen: Set = setOf (2, 3, 11)

Tijdens runtime zijn de typegegevens voor Set en Set zal worden gewist en we zien ze allebei als duidelijk Sets. Dus ook al is het perfect mogelijk om er tijdens runtime achter te komen dat de waarde a is Set, we kunnen niet zeggen of het een Set van strings, gehele getallen of iets anders: die informatie is gewist.

Dus, hoe voorkomt de compiler van Kotlin dat we een Geen tekenreeks in een Set? Of, als we een element uit een Set, hoe weet het dat het element een is Draad?

Het antwoord is simpel. De compiler is verantwoordelijk voor het wissen van de type-informatie maar daarvoor kent het eigenlijk de boeken variabele bevat Draad elementen.

Dus elke keer dat we er een element uit halen, cast de compiler het naar een Draad of als we er een element aan gaan toevoegen, typt de compiler check the input.

6.2. Veranderde typeparameters

Laten we meer plezier hebben met generieke geneesmiddelen en een extensiefunctie maken om te filteren Verzameling elementen op basis van hun type:

fun Iterable.filterIsInstance () = filter {het is T} Fout: kan niet controleren bijvoorbeeld het gewiste type: T

De "het is t" part controleert voor elk collection-element of het element een instantie van het type is T, maar aangezien de type-informatie tijdens runtime is gewist, kunnen we op deze manier niet nadenken over typeparameters.

Of kunnen we?

De regel voor het wissen van typen is in het algemeen waar, maar er is één geval waarin we deze beperking kunnen vermijden: Inline-functies. Typeparameters van inline-functies kunnen zijn reified, dus we kunnen tijdens runtime naar die typeparameters verwijzen.

De hoofdtekst van inline-functies is inline. Dat wil zeggen, de compiler vervangt de body rechtstreeks naar plaatsen waar de functie wordt aangeroepen in plaats van de normale functie-aanroep.

Als we de vorige functie declareren als in lijn en markeer de typeparameter als reified, dan hebben we tijdens runtime toegang tot algemene typegegevens:

inline fun Iterable.filterIsInstance () = filter {het is T}

De inline reificatie werkt als een zonnetje:

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

Laten we nog een voorbeeld schrijven. We kennen allemaal die typische SLF4j Logger definities:

klasse Gebruiker {privé val log = LoggerFactory.getLogger (Gebruiker :: class.java) // ...}

Met behulp van reified inline-functies kunnen we eleganter en minder syntaxis-gruwelijk schrijven Logger definities:

inline leuke logger (): Logger = LoggerFactory.getLogger (T :: class.java)

Dan kunnen we schrijven:

klasse Gebruiker {privé val log = logger () // ...}

Dit geeft ons een schonere optie om logboekregistratie te implementeren, op de Kotlin-manier.

6.3. Duik diep in Inline Reification

Dus, wat is er zo speciaal aan inline-functies, zodat type-reificatie alleen met hen werkt? Zoals we weten, kopieert de compiler van Kotlin de bytecode van inline-functies naar plaatsen waar de functie wordt aangeroepen.

Aangezien de compiler op elke call-site het exacte parametertype kent, kan deze de generieke typeparameter vervangen door de werkelijke typeverwijzingen.

Als we bijvoorbeeld schrijven:

klasse Gebruiker {privé val log = logger () // ...}

Wanneer de compiler het logger () functieaanroep, het kent de feitelijke generieke typeparameter -Gebruiker. Dus in plaats van de type-informatie te wissen, grijpt de compiler de reificatiemogelijkheid aan en reificeert de feitelijke typeparameter.

7. Conclusie

In dit artikel keken we naar de Kotlin Generic-typen. We hebben gezien hoe we de uit en in trefwoorden correct. We hebben typeprojecties gebruikt en een generieke methode gedefinieerd die gebruikmaakt van generieke beperkingen.

De implementatie van al deze voorbeelden en codefragmenten is te vinden in het GitHub-project - dit is een Maven-project, dus het moet gemakkelijk te importeren en uit te voeren zijn zoals het is.


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