Uitbreidingsmethoden in Kotlin

1. Inleiding

Kotlin introduceert het concept van uitbreidingsmethoden - en dat zijn een handige manier om bestaande klassen uit te breiden met een nieuwe functionaliteit zonder gebruik te maken van overerving of enige vorm van het Decorator-patroon - na het definiëren van een extensie. we kunnen het in wezen gebruiken - aangezien het het onderdeel was van de originele API.

Dit kan erg handig zijn om onze code gemakkelijk te lezen en te onderhouden te maken, omdat we methoden kunnen toevoegen die specifiek zijn voor onze behoeften en die het lijken alsof ze deel uitmaken van de originele code, zelfs als we geen toegang hebben tot de bronnen.

Het is bijvoorbeeld mogelijk dat we XML-escaping moeten uitvoeren op een Draad. In standaard Java-code zouden we een methode moeten schrijven die dit kan uitvoeren en deze noemen:

String escaped = escapeStringForXml (invoer);

Terwijl het in Kotlin is geschreven, zou het fragment kunnen worden vervangen door:

val escaped = input.escapeForXml ()

Dit is niet alleen gemakkelijker te lezen, maar IDE's kunnen de methode ook aanbieden als een optie voor automatisch aanvullen, net alsof het een standaardmethode op de Draad klasse.

2. Standaard methoden voor bibliotheekuitbreiding

De Kotlin Standard Library wordt standaard geleverd met enkele uitbreidingsmethoden.

2.1. Context aanpassen van uitbreidingsmethoden

Er zijn enkele generieke extensies die op alle typen in onze applicatie kunnen worden toegepast. Deze kunnen worden gebruikt om ervoor te zorgen dat code in een geschikte context wordt uitgevoerd en in sommige gevallen om ervoor te zorgen dat een variabele niet nul is.

Het blijkt dat we hoogstwaarschijnlijk extensies gebruiken zonder dit te beseffen.

Een van de meest populaire is mogelijk de laat() methode, die kan worden aangeroepen op elk type in Kotlin - laten we er een functie aan doorgeven die zal worden uitgevoerd op de initiële waarde:

val name = "Baeldung" val hoofdletters = naam .let {n -> n.toUpperCase ()}

Het is vergelijkbaar met de kaart() methode van Optioneel of Stroom klassen - in dit geval geven we een functie door die een actie vertegenwoordigt die een gegeven omzet Draad in de weergave in het bovenste omhulsel.

De variabele naam staat bekend als de ontvanger van de oproep omdat het de variabele is waarop de uitbreidingsmethode inwerkt.

Dit werkt prima met de safe-call-operator:

val name = misschienGetName () val hoofdletters = naam? .let {n -> n.toUpperCase ()}

In dit geval is het blok doorgegeven aan laat() wordt alleen geëvalueerd als de variabele naam was niet nul. Dit betekent dat binnen het blok de waarde n is gegarandeerd niet-nul. Hierover meer.

Er zijn andere alternatieven voor laat() dat kan echter ook nuttig zijn, afhankelijk van onze behoeften.

De rennen() extensie werkt hetzelfde als laat(), maar een ontvanger wordt geleverd als de dit waarde binnen het opgeroepen blok:

val name = "Baeldung" val hoofdletters = naam.run {toUpperCase ()}

van toepassing zijn() werkt hetzelfde als rennen(), maar het retourneert een ontvanger in plaats van de waarde van het opgegeven blok te retourneren.

Laten we profiteren van van toepassing zijn() om gerelateerde oproepen te koppelen:

val languages ​​= mutableListOf () languages.apply {add ("Java") add ("Kotlin") add ("Groovy") add ("Python")} .apply {remove ("Python")} 

Merk op hoe onze code beknopter en expressiever wordt en niet expliciet hoeft te gebruiken dit of het.

De ook () extensie werkt net als laat(), maar het geeft de ontvanger op dezelfde manier terug van toepassing zijn() doet:

val languages ​​= mutableListOf () languages.also {list -> list.add ("Java") list.add ("Kotlin") list.add ("Groovy")} 

De takeIf () extensie wordt voorzien van een predikaat dat inwerkt op de ontvanger, en als dit predikaat terugkeert waar dan geeft het de ontvanger terug of nul anders werkt dit op dezelfde manier als een combinatie van een gemeenschappelijke kaart() en filter() methoden:

val language = getLanguageUsed () val coolLanguage = language.takeIf {l -> l == "Kotlin"} 

De takeUnless () extensie is hetzelfde als takeIf () maar met de omgekeerde predikaatlogica.

val language = getLanguageUsed () val oldLanguage = language.takeUnless {l -> l == "Kotlin"} 

2.2. Uitbreidingsmethoden voor verzamelingen

Kotlin voegt een groot aantal uitbreidingsmethoden toe aan de standaard Java-verzamelingen die het werken met onze code vergemakkelijken.

Deze methoden bevinden zich binnenin _Collections.kt, _Ranges.kt, en _Sequences.kt, net zoals _Arrays.kt voor gelijkwaardige methoden om op toe te passen Arrays in plaats daarvan. (Onthoud dat in Kotlin, Arrays kan op dezelfde manier worden behandeld als Collecties)

Er zijn veel te veel van deze uitbreidingsmethoden om hier te bespreken, dus blader door deze bestanden om te zien wat er beschikbaar is.

Naast Collections voegt Kotlin een aanzienlijk aantal uitbreidingsmethoden toe aan het Draad klasse - gedefinieerd in _Strings.kt. Deze stellen ons in staat om te behandelen Snaren alsof het verzamelingen karakters zijn.

Al deze uitbreidingsmethoden werken samen om ons in staat te stellen aanzienlijk schonere, gemakkelijker te onderhouden code te schrijven, ongeacht het soort verzameling waarmee we werken.

3. Onze uitbreidingsmethoden schrijven

Dus, wat als we een klasse moeten uitbreiden met een nieuwe functionaliteit - ofwel vanuit de Java of Kotlin Standard Library of vanuit een afhankelijke bibliotheek die we gebruiken?

Extensiemethoden zijn geschreven als elke andere methode, maar de ontvangerklasse wordt geleverd als onderdeel van de functienaam, gescheiden door de punt.

Bijvoorbeeld:

leuk String.escapeForXml (): String {....}

Dit definieert een nieuwe functie genaamd escapeForXml als uitbreiding op de Draad class, waardoor we het kunnen noemen zoals hierboven beschreven.

Binnen deze functie hebben we toegang tot de ontvanger met dit, hetzelfde alsof we dit in de Draad klasse zelf:

fun String.escapeForXml (): String {return this .replace ("&", "&") .replace ("<", "", ">")}

3.1. Generieke uitbreidingsmethoden schrijven

Wat als we een uitbreidingsmethode willen schrijven die bedoeld is om in het algemeen op meerdere typen te worden toegepast? We kunnen het Ieder type, - wat het equivalent is van de Voorwerp class in Java - maar er is een betere manier.

Uitbreidingsmethoden kunnen zowel op een generieke als op een concrete ontvanger worden toegepast:

fun T.concatAsString (b: T): String {return this.toString () + b.toString ()}

Dit kan worden toegepast op elk type dat voldoet aan de generieke vereisten, en binnen de functie dit waarde is typenveilig.

Gebruik bijvoorbeeld het bovenstaande voorbeeld:

5.concatAsString (10) // compileert "5" .concatAsString ("10") // compileert 5.concatAsString ("10") // compileert niet

3.2. Tussenvoegseluitbreidingsmethoden schrijven

Tussenvoegselmethoden zijn handig voor het schrijven van code in DSL-stijl, omdat ze het mogelijk maken om methoden aan te roepen zonder de punt of haakjes:

tussenvoegsel leuk Number.toPowerOf (exponent: Number): Double {return Math.pow (this.toDouble (), exponent.toDouble ())}

We kunnen dit nu hetzelfde noemen als elke andere tussenvoegselmethode:

3 tot Vermogen van 2 // 9 9 tot Vermogen van 0,5 // 3

3.3. Operator-uitbreidingsmethoden schrijven

We zouden ook een operatormethode kunnen schrijven als een extensie.

Operatormethoden zijn methoden waarmee we kunnen profiteren van de verkorte naam van de operator in plaats van de volledige naam van de methode - bijv. De plus operatormethode kan worden aangeroepen met de + operator:

operator fun List.times (by: Int): List {return this.map {it * by}}

Nogmaals, dit werkt hetzelfde als elke andere operatormethode:

listOf (1, 2, 3) * 4 // [4, 8, 12]

4. Bellen met Kotlin-uitbreidingsfunctie vanuit Java

Laten we nu eens kijken hoe Java werkt met Kotlin-uitbreidingsfuncties.

Over het algemeen is elke uitbreidingsmethode die we in Kotlin definiëren beschikbaar voor gebruik in Java. We moeten echter niet vergeten dat de infix methode moet nog steeds worden aangeroepen met punt en haakjes. Hetzelfde met operator-extensies - we kunnen niet alleen het plusteken (+) gebruiken. Deze faciliteiten zijn alleen beschikbaar in Kotlin.

We kunnen echter enkele van de standaard Kotlin-bibliotheekmethoden in Java, zoals laat of van toepassing zijn, omdat ze zijn gemarkeerd met @InlineOnly.

4.1. Zichtbaarheid van de aangepaste extensiefunctie in Java

Laten we een van de eerder gedefinieerde extensiefuncties gebruiken - String.escapeXml (). Ons bestand met de extensiemethode heet StringUtil.kt.

Als we nu een extensiemethode vanuit Java moeten aanroepen, moeten we een klassenaam gebruiken StringUtilKt. Merk op dat we de Kt achtervoegsel:

String xml = "hi"; String escapedXml = StringUtilKt.escapeForXml (xml); assertEquals ("hi", escapedXml);

Let op de eerste escapeForXml parameter. Dit extra argument is een ontvangertype van de uitbreidingsfunctie. Kotlin met extensiefunctie op het hoogste niveau is een pure Java klasse met een statische methode. Daarom moet het op de een of andere manier het origineel passeren Draad.

En natuurlijk, net als in Java, we kunnen statische import gebruiken:

importeer statische com.baeldung.kotlin.StringUtilKt. *;

4.2. Een ingebouwde Kotlin-uitbreidingsmethode aanroepen

Kotlin helpt ons code gemakkelijker en sneller te schrijven door veel ingebouwde extensiefuncties te bieden. Er is bijvoorbeeld de Draad.hoofdletter () methode, die rechtstreeks kan worden aangeroepen vanuit Java:

String name = "john"; String capitalizedName = StringsKt.capitalize (naam); assertEquals ("John", capitalizedName);

Echter, we kunnen geen extensiemethoden aanroepen die zijn gemarkeerd met @InlineOnly van Java, bijvoorbeeld:

inline leuke T.let (blok: (T) -> R): R

4.3. Hernoemen van de gegenereerde Java Static Class

We weten al dat a Kotlin extensiefunctie is statisch Java methode. Laten we een gegenereerd Java klas met een annotatie @file: JvmName (naam: String).

Dit moet bovenaan het bestand worden toegevoegd:

@file: JvmName ("Strings") pakket com.baeldung.kotlin plezier String.escapeForXml (): String {retourneer deze .replace ("&", "&") .replace ("<", "", ">" )}

Als we nu een extensiemethode willen aanroepen, hoeven we alleen de Snaren naam van de klasse:

Strings.escapeForXml (xml);

We kunnen ook nog steeds een statische import toevoegen:

importeer statische com.baeldung.kotlin.Strings. *;

5. Samenvatting

Uitbreidingsmethoden zijn handige hulpmiddelen om typen die al in het systeem bestaan ​​uit te breiden - ofwel omdat ze niet de functionaliteit hebben die we nodig hebben of simpelweg om een ​​bepaald codegebied gemakkelijker te beheren te maken.

We hebben hier enkele uitbreidingsmethoden gezien die klaar zijn voor gebruik in het systeem. Daarnaast hebben we verschillende mogelijkheden van uitbreidingsmethoden onderzocht. Enkele voorbeelden van deze functionaliteit zijn te vinden op GitHub.